feat: initial commit
This commit is contained in:
@ -0,0 +1,127 @@
|
||||
package component
|
||||
|
||||
import (
|
||||
common "forge.cadoles.com/wpetit/kouiz/internal/http/handler/webui/common/component"
|
||||
"forge.cadoles.com/wpetit/kouiz/internal/store"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type LeaderboardPageVModel struct {
|
||||
Player *store.Player
|
||||
Players []*store.Player
|
||||
PlayerRank int
|
||||
}
|
||||
|
||||
templ LeaderboardPage(vmodel LeaderboardPageVModel) {
|
||||
@common.AppPage(common.WithPageOptions(
|
||||
common.WithTitle("Tableau des scores"),
|
||||
)) {
|
||||
<h3 class="title">Podium</h3>
|
||||
<div class="podium">
|
||||
<div class="podium-position podium-second">
|
||||
if len(vmodel.Players) > 1 {
|
||||
<div class="podium-player">
|
||||
<span class="has-text-weight-bold is-uppercase">{ vmodel.Players[1].Name }</span>
|
||||
<br/>
|
||||
<span class="has-text-grey is-size-7">{ strconv.FormatInt(int64(vmodel.Players[1].Score), 10) }pts</span>
|
||||
</div>
|
||||
}
|
||||
<div class="podium-step">2ème</div>
|
||||
</div>
|
||||
<div class="podium-position podium-first">
|
||||
if len(vmodel.Players) > 0 {
|
||||
<div class="podium-player">
|
||||
<span class="icon is-size-4 has-text-warning"><i class="fas fa-trophy"></i></span>
|
||||
<br/>
|
||||
<span class="has-text-weight-bold is-uppercase">{ vmodel.Players[0].Name }</span>
|
||||
<br/>
|
||||
<span class="has-text-grey is-size-7">{ strconv.FormatInt(int64(vmodel.Players[0].Score), 10) }pts</span>
|
||||
</div>
|
||||
}
|
||||
<div class="podium-step">1er</div>
|
||||
</div>
|
||||
<div class="podium-position podium-third">
|
||||
if len(vmodel.Players) > 2 {
|
||||
<div class="podium-player">
|
||||
<span class="has-text-weight-bold is-uppercase">{ vmodel.Players[2].Name }</span>
|
||||
<br/>
|
||||
<span class="has-text-grey is-size-7">{ strconv.FormatInt(int64(vmodel.Players[2].Score), 10) }pts</span>
|
||||
</div>
|
||||
}
|
||||
<div class="podium-step">3ème</div>
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="title mt-5">Tableau des scores</h3>
|
||||
<div class="table-container">
|
||||
<table class="table is-fullwidth">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Position</th>
|
||||
<th>Pseudonyme</th>
|
||||
<th>Score</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
for i, p := range vmodel.Players {
|
||||
<tr
|
||||
if p.ID == vmodel.Player.ID {
|
||||
class="has-text-weight-bold is-info"
|
||||
}
|
||||
>
|
||||
<td>{ strconv.FormatInt(int64(i+1), 10) }</td>
|
||||
<td>{ p.Name }</td>
|
||||
<td>{ strconv.FormatInt(int64(p.Score), 10) }</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<style>
|
||||
.podium {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.podium .podium-step {
|
||||
width: 100px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
text-shadow: 1px 1px #333;
|
||||
}
|
||||
|
||||
.podium .podium-position {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.podium .podium-player {
|
||||
text-shadow: none;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.podium .podium-first .podium-step {
|
||||
height: 70px;
|
||||
background-color: hsl(141, 71%, 48%) ;
|
||||
}
|
||||
|
||||
.podium .podium-second .podium-step {
|
||||
height: 50px;
|
||||
background-color: hsl(204, 86%, 53%);
|
||||
}
|
||||
|
||||
.podium .podium-third .podium-step {
|
||||
height: 30px;
|
||||
background-color: #66d1ff;
|
||||
}
|
||||
</style>
|
||||
}
|
||||
}
|
@ -0,0 +1,239 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.819
|
||||
package component
|
||||
|
||||
//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 (
|
||||
common "forge.cadoles.com/wpetit/kouiz/internal/http/handler/webui/common/component"
|
||||
"forge.cadoles.com/wpetit/kouiz/internal/store"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type LeaderboardPageVModel struct {
|
||||
Player *store.Player
|
||||
Players []*store.Player
|
||||
PlayerRank int
|
||||
}
|
||||
|
||||
func LeaderboardPage(vmodel LeaderboardPageVModel) 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, "<h3 class=\"title\">Podium</h3><div class=\"podium\"><div class=\"podium-position podium-second\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if len(vmodel.Players) > 1 {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<div class=\"podium-player\"><span class=\"has-text-weight-bold is-uppercase\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(vmodel.Players[1].Name)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/quiz/component/leaderboard_page.templ`, Line: 24, Col: 78}
|
||||
}
|
||||
_, 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, "</span><br><span class=\"has-text-grey is-size-7\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.FormatInt(int64(vmodel.Players[1].Score), 10))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/quiz/component/leaderboard_page.templ`, Line: 26, Col: 99}
|
||||
}
|
||||
_, 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, 4, "pts</span></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<div class=\"podium-step\">2ème</div></div><div class=\"podium-position podium-first\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if len(vmodel.Players) > 0 {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<div class=\"podium-player\"><span class=\"icon is-size-4 has-text-warning\"><i class=\"fas fa-trophy\"></i></span><br><span class=\"has-text-weight-bold is-uppercase\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 string
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(vmodel.Players[0].Name)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/quiz/component/leaderboard_page.templ`, Line: 36, Col: 78}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</span><br><span class=\"has-text-grey is-size-7\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.FormatInt(int64(vmodel.Players[0].Score), 10))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/quiz/component/leaderboard_page.templ`, Line: 38, Col: 99}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "pts</span></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<div class=\"podium-step\">1er</div></div><div class=\"podium-position podium-third\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if len(vmodel.Players) > 2 {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<div class=\"podium-player\"><span class=\"has-text-weight-bold is-uppercase\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(vmodel.Players[2].Name)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/quiz/component/leaderboard_page.templ`, Line: 46, Col: 78}
|
||||
}
|
||||
_, 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, 11, "</span><br><span class=\"has-text-grey is-size-7\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var8 string
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.FormatInt(int64(vmodel.Players[2].Score), 10))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/quiz/component/leaderboard_page.templ`, Line: 48, Col: 99}
|
||||
}
|
||||
_, 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, 12, "pts</span></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<div class=\"podium-step\">3ème</div></div></div><h3 class=\"title mt-5\">Tableau des scores</h3><div class=\"table-container\"><table class=\"table is-fullwidth\"><thead><tr><th>Position</th><th>Pseudonyme</th><th>Score</th></tr></thead> <tbody>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
for i, p := range vmodel.Players {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<tr")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if p.ID == vmodel.Player.ID {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, " class=\"has-text-weight-bold is-info\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "><td>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var9 string
|
||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.FormatInt(int64(i+1), 10))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/quiz/component/leaderboard_page.templ`, Line: 71, Col: 46}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</td><td>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var10 string
|
||||
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(p.Name)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/quiz/component/leaderboard_page.templ`, Line: 72, Col: 19}
|
||||
}
|
||||
_, 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, 18, "</td><td>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var11 string
|
||||
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.FormatInt(int64(p.Score), 10))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/quiz/component/leaderboard_page.templ`, Line: 73, Col: 50}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</td></tr>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "</tbody></table></div><style>\n .podium {\n display: flex;\n flex-direction: row;\n align-items: flex-end;\n justify-content: center;\n gap: 10px;\n }\n\n .podium .podium-step {\n width: 100px;\n justify-content: center;\n align-items: center;\n display: flex;\n font-weight: bold;\n color: white;\n text-shadow: 1px 1px #333;\n }\n\n .podium .podium-position {\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n }\n\n .podium .podium-player {\n text-shadow: none;\n color: #333;\n text-align: center;\n }\n\n .podium .podium-first .podium-step {\n height: 70px;\n background-color: hsl(141, 71%, 48%)\t;\n }\n\n .podium .podium-second .podium-step {\n height: 50px;\n background-color: hsl(204, 86%, 53%);\n }\n\n .podium .podium-third .podium-step {\n height: 30px;\n background-color: #66d1ff;\n }\n </style>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
templ_7745c5c3_Err = common.AppPage(common.WithPageOptions(
|
||||
common.WithTitle("Tableau des scores"),
|
||||
)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
@ -3,22 +3,37 @@ package component
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"forge.cadoles.com/wpetit/kouiz/internal/http/form"
|
||||
common "forge.cadoles.com/wpetit/kouiz/internal/http/handler/webui/common/component"
|
||||
"forge.cadoles.com/wpetit/kouiz/internal/store"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/yuin/goldmark"
|
||||
"log/slog"
|
||||
"slices"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type QuizPageVModel struct {
|
||||
Player *store.Player
|
||||
Player *store.Player
|
||||
CurrentTurn *store.QuizTurn
|
||||
}
|
||||
|
||||
func NewQuizForm() *form.Form {
|
||||
func NewSelectEntryForm() *form.Form {
|
||||
return form.New(
|
||||
form.NewField(
|
||||
"project",
|
||||
"entry",
|
||||
form.Attrs{},
|
||||
form.NonEmpty("Ce champ ne doit pas être vide."),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func NewAnswerForm() *form.Form {
|
||||
return form.New(
|
||||
form.NewField(
|
||||
"answer",
|
||||
form.Attrs{},
|
||||
form.NonEmpty("Ce champ ne doit pas être vide."),
|
||||
),
|
||||
@ -29,6 +44,117 @@ templ QuizPage(vmodel QuizPageVModel) {
|
||||
@common.AppPage(common.WithPageOptions(
|
||||
common.WithTitle("Quiz"),
|
||||
)) {
|
||||
<h2 class="title is-size-3">Tour #{ strconv.FormatUint(uint64(vmodel.CurrentTurn.ID), 10) }</h2>
|
||||
if vmodel.Player.PlayedAt.After(vmodel.CurrentTurn.StartedAt) {
|
||||
<div class="content has-text-centered is-size-5">
|
||||
<p><strong>Vous avez déjà joué ce tour ci !</strong></p>
|
||||
<p>Le prochain tour commencera dans { vmodel.CurrentTurn.EndedAt.Sub(time.Now().UTC()).Round(time.Minute).String() }.</p>
|
||||
</div>
|
||||
} else if vmodel.Player.SelectedEntry == nil || vmodel.Player.SelectedTurn == nil || *vmodel.Player.SelectedTurn != vmodel.CurrentTurn.ID {
|
||||
@QuizQuestionSelector(vmodel)
|
||||
} else {
|
||||
@QuizQuestion(vmodel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
templ QuizQuestionSelector(vmodel QuizPageVModel) {
|
||||
<form action={ common.BaseURL(ctx, common.WithPath("/quiz/select")) } method="post">
|
||||
<h3 class="title is-size-3">Choisissez votre prochaine question</h3>
|
||||
<div class="message is-info">
|
||||
<div class="message-body">
|
||||
<p>
|
||||
<strong>Attention</strong>, une fois la thématique sélectionnée vous aurez <strong>30 secondes pour répondre</strong>. Faites le bon choix !
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
for _, entry := range vmodel.CurrentTurn.Entries {
|
||||
<div class="box">
|
||||
<div class="level mx-5">
|
||||
<div class="level-left">
|
||||
<div class="level-item">
|
||||
<div class="content">
|
||||
<p class="title">
|
||||
{ entry.Category.Theme }
|
||||
</p>
|
||||
<p class="subtitle">{ entry.Category.Name }</p>
|
||||
<div class="content is-italic">
|
||||
<p>{ entry.Category.Description }</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-right">
|
||||
<div class="level-item">
|
||||
<div class="content">
|
||||
<p class="is-size-4 is-family-secondary has-text-right" style="width:300px">
|
||||
<span></span> <span class="has-text-weight-bold">{ difficultyLevel(entry.Level) }</span>
|
||||
<br/>
|
||||
<span>+{ strconv.FormatInt(int64((entry.Level+1)*2), 10) } points</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-item">
|
||||
<button class="button is-large ml-5" name="entry" value={ strconv.FormatInt(int64(entry.ID), 10) }>
|
||||
<span class="icon"><i class="fas fa-chevron-right"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</form>
|
||||
}
|
||||
|
||||
templ QuizQuestion(vmodel QuizPageVModel) {
|
||||
{{
|
||||
selectedEntryIndex := slices.IndexFunc(vmodel.CurrentTurn.Entries, func(e *store.QuizEntry) bool {
|
||||
return vmodel.Player.SelectedEntry != nil && *vmodel.Player.SelectedEntry == e.ID
|
||||
})
|
||||
}}
|
||||
{{ selectedEntry := vmodel.CurrentTurn.Entries[selectedEntryIndex] }}
|
||||
<div class="content">
|
||||
<p class="title is-size-3">
|
||||
{ selectedEntry.Question }
|
||||
</p>
|
||||
<p class="subtitle">{ selectedEntry.Category.Name } - { selectedEntry.Category.Theme }</p>
|
||||
</div>
|
||||
<form action={ common.BaseURL(ctx, common.WithPath("/quiz/answer")) } method="post">
|
||||
for index, proposition := range selectedEntry.Propositions {
|
||||
<div class="box">
|
||||
<div class="level">
|
||||
<div class="level-left">
|
||||
<div class="level-item">
|
||||
<div class="content">
|
||||
<p class="has-font-weight-bold is-size-4">
|
||||
<span class="has-text-grey ">{ strconv.FormatInt(int64(index), 10) }.</span> <span class="is-family-secondary">{ proposition }</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-right">
|
||||
<div class="level-item">
|
||||
<button class="button is-large ml-5" name="answer" value={ strconv.FormatInt(int64(index), 10) }>
|
||||
<span class="icon"><i class="fas fa-chevron-right"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</form>
|
||||
}
|
||||
|
||||
func difficultyLevel(level uint) string {
|
||||
switch level {
|
||||
case 0:
|
||||
return "Débutant"
|
||||
case 1:
|
||||
return "Confirmé"
|
||||
case 2:
|
||||
return "Expert"
|
||||
default:
|
||||
return fmt.Sprintf("Hors catégorie (%d)", level)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,22 +11,37 @@ import templruntime "github.com/a-h/templ/runtime"
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"forge.cadoles.com/wpetit/kouiz/internal/http/form"
|
||||
common "forge.cadoles.com/wpetit/kouiz/internal/http/handler/webui/common/component"
|
||||
"forge.cadoles.com/wpetit/kouiz/internal/store"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/yuin/goldmark"
|
||||
"log/slog"
|
||||
"slices"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type QuizPageVModel struct {
|
||||
Player *store.Player
|
||||
Player *store.Player
|
||||
CurrentTurn *store.QuizTurn
|
||||
}
|
||||
|
||||
func NewQuizForm() *form.Form {
|
||||
func NewSelectEntryForm() *form.Form {
|
||||
return form.New(
|
||||
form.NewField(
|
||||
"project",
|
||||
"entry",
|
||||
form.Attrs{},
|
||||
form.NonEmpty("Ce champ ne doit pas être vide."),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func NewAnswerForm() *form.Form {
|
||||
return form.New(
|
||||
form.NewField(
|
||||
"answer",
|
||||
form.Attrs{},
|
||||
form.NonEmpty("Ce champ ne doit pas être vide."),
|
||||
),
|
||||
@ -66,6 +81,52 @@ func QuizPage(vmodel QuizPageVModel) templ.Component {
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<h2 class=\"title is-size-3\">Tour #")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.FormatUint(uint64(vmodel.CurrentTurn.ID), 10))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/quiz/component/quiz_page.templ`, Line: 47, Col: 91}
|
||||
}
|
||||
_, 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, 2, "</h2>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if vmodel.Player.PlayedAt.After(vmodel.CurrentTurn.StartedAt) {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<div class=\"content has-text-centered is-size-5\"><p><strong>Vous avez déjà joué ce tour ci !</strong></p><p>Le prochain tour commencera dans ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(vmodel.CurrentTurn.EndedAt.Sub(time.Now().UTC()).Round(time.Minute).String())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/quiz/component/quiz_page.templ`, Line: 51, Col: 118}
|
||||
}
|
||||
_, 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, 4, ".</p></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else if vmodel.Player.SelectedEntry == nil || vmodel.Player.SelectedTurn == nil || *vmodel.Player.SelectedTurn != vmodel.CurrentTurn.ID {
|
||||
templ_7745c5c3_Err = QuizQuestionSelector(vmodel).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = QuizQuestion(vmodel).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
templ_7745c5c3_Err = common.AppPage(common.WithPageOptions(
|
||||
@ -78,6 +139,276 @@ func QuizPage(vmodel QuizPageVModel) templ.Component {
|
||||
})
|
||||
}
|
||||
|
||||
func QuizQuestionSelector(vmodel QuizPageVModel) 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, 5, "<form action=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 templ.SafeURL = common.BaseURL(ctx, common.WithPath("/quiz/select"))
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var6)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\" method=\"post\"><h3 class=\"title is-size-3\">Choisissez votre prochaine question</h3><div class=\"message is-info\"><div class=\"message-body\"><p><strong>Attention</strong>, une fois la thématique sélectionnée vous aurez <strong>30 secondes pour répondre</strong>. Faites le bon choix !</p></div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
for _, entry := range vmodel.CurrentTurn.Entries {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<div class=\"box\"><div class=\"level mx-5\"><div class=\"level-left\"><div class=\"level-item\"><div class=\"content\"><p class=\"title\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(entry.Category.Theme)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/quiz/component/quiz_page.templ`, Line: 78, Col: 31}
|
||||
}
|
||||
_, 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, "</p><p class=\"subtitle\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var8 string
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(entry.Category.Name)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/quiz/component/quiz_page.templ`, Line: 80, Col: 49}
|
||||
}
|
||||
_, 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, "</p><div class=\"content is-italic\"><p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var9 string
|
||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(entry.Category.Description)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/quiz/component/quiz_page.templ`, Line: 82, Col: 40}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</p></div></div></div></div><div class=\"level-right\"><div class=\"level-item\"><div class=\"content\"><p class=\"is-size-4 is-family-secondary has-text-right\" style=\"width:300px\"><span></span> <span class=\"has-text-weight-bold\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var10 string
|
||||
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(difficultyLevel(entry.Level))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/quiz/component/quiz_page.templ`, Line: 91, Col: 88}
|
||||
}
|
||||
_, 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, 11, "</span><br><span>+")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var11 string
|
||||
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.FormatInt(int64((entry.Level+1)*2), 10))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/quiz/component/quiz_page.templ`, Line: 93, Col: 65}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, " points</span></p></div></div><div class=\"level-item\"><button class=\"button is-large ml-5\" name=\"entry\" value=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var12 string
|
||||
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.FormatInt(int64(entry.ID), 10))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/quiz/component/quiz_page.templ`, Line: 98, Col: 103}
|
||||
}
|
||||
_, 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, 13, "\"><span class=\"icon\"><i class=\"fas fa-chevron-right\"></i></span></button></div></div></div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</form>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func QuizQuestion(vmodel QuizPageVModel) 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_Var13 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var13 == nil {
|
||||
templ_7745c5c3_Var13 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
|
||||
selectedEntryIndex := slices.IndexFunc(vmodel.CurrentTurn.Entries, func(e *store.QuizEntry) bool {
|
||||
return vmodel.Player.SelectedEntry != nil && *vmodel.Player.SelectedEntry == e.ID
|
||||
})
|
||||
selectedEntry := vmodel.CurrentTurn.Entries[selectedEntryIndex]
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<div class=\"content\"><p class=\"title is-size-3\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var14 string
|
||||
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(selectedEntry.Question)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/quiz/component/quiz_page.templ`, Line: 118, Col: 27}
|
||||
}
|
||||
_, 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, 16, "</p><p class=\"subtitle\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var15 string
|
||||
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(selectedEntry.Category.Name)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/quiz/component/quiz_page.templ`, Line: 120, Col: 51}
|
||||
}
|
||||
_, 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, 17, " - ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var16 string
|
||||
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(selectedEntry.Category.Theme)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/quiz/component/quiz_page.templ`, Line: 120, Col: 86}
|
||||
}
|
||||
_, 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, 18, "</p></div><form action=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var17 templ.SafeURL = common.BaseURL(ctx, common.WithPath("/quiz/answer"))
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var17)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "\" method=\"post\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
for index, proposition := range selectedEntry.Propositions {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<div class=\"box\"><div class=\"level\"><div class=\"level-left\"><div class=\"level-item\"><div class=\"content\"><p class=\"has-font-weight-bold is-size-4\"><span class=\"has-text-grey\t\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var18 string
|
||||
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.FormatInt(int64(index), 10))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/quiz/component/quiz_page.templ`, Line: 130, Col: 75}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, ".</span> <span class=\"is-family-secondary\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var19 string
|
||||
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(proposition)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/quiz/component/quiz_page.templ`, Line: 130, Col: 133}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "</span></p></div></div></div><div class=\"level-right\"><div class=\"level-item\"><button class=\"button is-large ml-5\" name=\"answer\" value=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var20 string
|
||||
templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.FormatInt(int64(index), 10))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/quiz/component/quiz_page.templ`, Line: 137, Col: 101}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "\"><span class=\"icon\"><i class=\"fas fa-chevron-right\"></i></span></button></div></div></div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</form>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func difficultyLevel(level uint) string {
|
||||
switch level {
|
||||
case 0:
|
||||
return "Débutant"
|
||||
case 1:
|
||||
return "Confirmé"
|
||||
case 2:
|
||||
return "Expert"
|
||||
default:
|
||||
return fmt.Sprintf("Hors catégorie (%d)", level)
|
||||
}
|
||||
}
|
||||
|
||||
func markdownToHTML(ctx context.Context, text string) string {
|
||||
var buff bytes.Buffer
|
||||
if err := goldmark.Convert([]byte(text), &buff); err != nil {
|
||||
|
47
internal/http/handler/webui/quiz/component/result_page.templ
Normal file
47
internal/http/handler/webui/quiz/component/result_page.templ
Normal file
@ -0,0 +1,47 @@
|
||||
package component
|
||||
|
||||
import (
|
||||
common "forge.cadoles.com/wpetit/kouiz/internal/http/handler/webui/common/component"
|
||||
"forge.cadoles.com/wpetit/kouiz/internal/store"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type ResultPageVModel struct {
|
||||
Player *store.Player
|
||||
PlayerRank int
|
||||
Entry *store.QuizEntry
|
||||
Won bool
|
||||
}
|
||||
|
||||
templ ResultPage(vmodel ResultPageVModel) {
|
||||
@common.AppPage(common.WithPageOptions(
|
||||
common.WithTitle("Quiz"),
|
||||
)) {
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<div class="content has-text-centered is-size-5">
|
||||
if vmodel.Won {
|
||||
<h3 class="title">Bien joué !</h3>
|
||||
} else {
|
||||
<h3 class="title">Dommage !</h3>
|
||||
}
|
||||
<p>La réponse attendue à la question <br/><span class="is-italic">"{ vmodel.Entry.Question }"</span> était <span class="is-italic">"{ vmodel.Entry.Answer }"</span>.</p>
|
||||
if vmodel.Won {
|
||||
<p>Vous avez gagné <strong>{ strconv.FormatInt(int64((vmodel.Entry.Level+1)*2), 10) } points</strong> !</p>
|
||||
}
|
||||
<p class="is-size-6 is-family-secondary has-text-grey">Vous avez actuellement { strconv.FormatInt(int64((vmodel.Player.Score)), 10) } points, ce qui vous place <a href={ common.BaseURL(ctx, common.WithPath("/quiz/leaderboard")) }>en position #{ strconv.FormatInt(int64(vmodel.PlayerRank), 10) } du classement.</a></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column">
|
||||
<div class="message is-info">
|
||||
<div class="message-header">
|
||||
Le saviez vous ?
|
||||
</div>
|
||||
<div class="message-body">
|
||||
{ vmodel.Entry.Anecdote }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
185
internal/http/handler/webui/quiz/component/result_page_templ.go
Normal file
185
internal/http/handler/webui/quiz/component/result_page_templ.go
Normal file
@ -0,0 +1,185 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.819
|
||||
package component
|
||||
|
||||
//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 (
|
||||
common "forge.cadoles.com/wpetit/kouiz/internal/http/handler/webui/common/component"
|
||||
"forge.cadoles.com/wpetit/kouiz/internal/store"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type ResultPageVModel struct {
|
||||
Player *store.Player
|
||||
PlayerRank int
|
||||
Entry *store.QuizEntry
|
||||
Won bool
|
||||
}
|
||||
|
||||
func ResultPage(vmodel ResultPageVModel) 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, "<div class=\"columns\"><div class=\"column\"><div class=\"content has-text-centered is-size-5\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if vmodel.Won {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<h3 class=\"title\">Bien joué !</h3>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<h3 class=\"title\">Dommage !</h3>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<p>La réponse attendue à la question <br><span class=\"is-italic\">\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(vmodel.Entry.Question)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/quiz/component/result_page.templ`, Line: 28, Col: 97}
|
||||
}
|
||||
_, 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, 5, "\"</span> était <span class=\"is-italic\">\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(vmodel.Entry.Answer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/quiz/component/result_page.templ`, Line: 28, Col: 161}
|
||||
}
|
||||
_, 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, 6, "\"</span>.</p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if vmodel.Won {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<p>Vous avez gagné <strong>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 string
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.FormatInt(int64((vmodel.Entry.Level+1)*2), 10))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/quiz/component/result_page.templ`, Line: 30, Col: 90}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, " points</strong> !</p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<p class=\"is-size-6 is-family-secondary has-text-grey\">Vous avez actuellement ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.FormatInt(int64((vmodel.Player.Score)), 10))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/quiz/component/result_page.templ`, Line: 32, Col: 136}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, " points, ce qui vous place <a href=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 templ.SafeURL = common.BaseURL(ctx, common.WithPath("/quiz/leaderboard"))
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var7)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\">en position #")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var8 string
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.FormatInt(int64(vmodel.PlayerRank), 10))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/quiz/component/result_page.templ`, Line: 32, Col: 297}
|
||||
}
|
||||
_, 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, 12, " du classement.</a></p></div></div><div class=\"column\"><div class=\"message is-info\"><div class=\"message-header\">Le saviez vous ?</div><div class=\"message-body\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var9 string
|
||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(vmodel.Entry.Anecdote)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/quiz/component/result_page.templ`, Line: 41, Col: 29}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</div></div></div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
templ_7745c5c3_Err = common.AppPage(common.WithPageOptions(
|
||||
common.WithTitle("Quiz"),
|
||||
)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
@ -5,12 +5,14 @@ import (
|
||||
"time"
|
||||
|
||||
"forge.cadoles.com/wpetit/kouiz/internal/store"
|
||||
"forge.cadoles.com/wpetit/kouiz/internal/timex"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
mux *http.ServeMux
|
||||
store *store.Store
|
||||
playInterval time.Duration
|
||||
playPeriod timex.PeriodType
|
||||
}
|
||||
|
||||
// ServeHTTP implements http.Handler.
|
||||
@ -18,15 +20,19 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
h.mux.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func NewHandler(store *store.Store, playInterval time.Duration) *Handler {
|
||||
func NewHandler(store *store.Store, playInterval time.Duration, playPeriod timex.PeriodType) *Handler {
|
||||
h := &Handler{
|
||||
mux: http.NewServeMux(),
|
||||
store: store,
|
||||
playInterval: playInterval,
|
||||
playPeriod: playPeriod,
|
||||
}
|
||||
|
||||
h.mux.HandleFunc("GET /", h.getQuizPage)
|
||||
h.mux.HandleFunc("POST /", h.handleQuizForm)
|
||||
h.mux.HandleFunc("GET /leaderboard", h.getLeadeboardPage)
|
||||
h.mux.HandleFunc("POST /select", h.handleSelectEntryForm)
|
||||
h.mux.HandleFunc("POST /answer", h.handleAnswerForm)
|
||||
h.mux.HandleFunc("GET /result", h.getResultPage)
|
||||
|
||||
return h
|
||||
}
|
||||
|
76
internal/http/handler/webui/quiz/leaderboard_page.go
Normal file
76
internal/http/handler/webui/quiz/leaderboard_page.go
Normal file
@ -0,0 +1,76 @@
|
||||
package quiz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"forge.cadoles.com/wpetit/kouiz/internal/http/handler/webui/common"
|
||||
"forge.cadoles.com/wpetit/kouiz/internal/http/handler/webui/quiz/component"
|
||||
"forge.cadoles.com/wpetit/kouiz/internal/store"
|
||||
"github.com/a-h/templ"
|
||||
"github.com/pkg/errors"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func (h *Handler) getLeadeboardPage(w http.ResponseWriter, r *http.Request) {
|
||||
vmodel, err := h.fillLeaderboardPageVModel(r)
|
||||
if err != nil {
|
||||
h.handleError(w, r, errors.WithStack(err))
|
||||
return
|
||||
}
|
||||
|
||||
resultPage := component.LeaderboardPage(*vmodel)
|
||||
templ.Handler(resultPage).ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func (h *Handler) fillLeaderboardPageVModel(r *http.Request) (*component.LeaderboardPageVModel, error) {
|
||||
vmodel := &component.LeaderboardPageVModel{}
|
||||
|
||||
err := common.FillViewModel(
|
||||
r.Context(), vmodel, r,
|
||||
h.fillLeaderboardPagePlayer,
|
||||
h.fillLeaderboardPagePlayers,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return vmodel, nil
|
||||
}
|
||||
|
||||
func (h *Handler) fillLeaderboardPagePlayer(ctx context.Context, vmodel *component.LeaderboardPageVModel, r *http.Request) error {
|
||||
player, err := h.getRequestPlayer(r)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
vmodel.Player = player
|
||||
|
||||
rank, err := h.store.GetPlayerRank(ctx, player.ID)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
vmodel.PlayerRank = rank
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Handler) fillLeaderboardPagePlayers(ctx context.Context, vmodel *component.LeaderboardPageVModel, r *http.Request) error {
|
||||
err := h.store.Do(ctx, func(db *gorm.DB) error {
|
||||
err := db.Model(&store.Player{}).
|
||||
Select("id", "name", "score").
|
||||
Order("score DESC").Find(&vmodel.Players).
|
||||
Error
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -3,14 +3,17 @@ package quiz
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
httpCtx "forge.cadoles.com/wpetit/kouiz/internal/http/context"
|
||||
"forge.cadoles.com/wpetit/kouiz/internal/http/handler/webui/auth"
|
||||
"forge.cadoles.com/wpetit/kouiz/internal/http/handler/webui/common"
|
||||
"forge.cadoles.com/wpetit/kouiz/internal/http/handler/webui/quiz/component"
|
||||
"forge.cadoles.com/wpetit/kouiz/internal/store"
|
||||
"github.com/a-h/templ"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/pkg/errors"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func (h *Handler) getQuizPage(w http.ResponseWriter, r *http.Request) {
|
||||
@ -20,8 +23,8 @@ func (h *Handler) getQuizPage(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
issue := component.QuizPage(*vmodel)
|
||||
templ.Handler(issue).ServeHTTP(w, r)
|
||||
quizPage := component.QuizPage(*vmodel)
|
||||
templ.Handler(quizPage).ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func (h *Handler) fillQuizPageVModel(r *http.Request) (*component.QuizPageVModel, error) {
|
||||
@ -40,15 +43,8 @@ func (h *Handler) fillQuizPageVModel(r *http.Request) (*component.QuizPageVModel
|
||||
}
|
||||
|
||||
func (h *Handler) fillQuizPagePlayer(ctx context.Context, vmodel *component.QuizPageVModel, r *http.Request) error {
|
||||
user := auth.ContextUser(ctx)
|
||||
|
||||
player := &store.Player{
|
||||
Name: user.Name,
|
||||
UserID: user.ID,
|
||||
UserProvider: user.Provider,
|
||||
}
|
||||
|
||||
if err := h.store.UpsertPlayer(ctx, player); err != nil {
|
||||
player, err := h.getRequestPlayer(r)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
@ -58,20 +54,38 @@ func (h *Handler) fillQuizPagePlayer(ctx context.Context, vmodel *component.Quiz
|
||||
}
|
||||
|
||||
func (h *Handler) fillQuizPageTurn(ctx context.Context, vmodel *component.QuizPageVModel, r *http.Request) error {
|
||||
turn, err := h.store.GetQuizTurn(ctx, h.playInterval)
|
||||
turn, err := h.store.GetQuizTurn(ctx, h.playInterval, h.playPeriod)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
spew.Dump(turn)
|
||||
vmodel.CurrentTurn = turn
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Handler) handleQuizForm(w http.ResponseWriter, r *http.Request) {
|
||||
quizForm := component.NewQuizForm()
|
||||
func (h *Handler) getRequestPlayer(r *http.Request) (*store.Player, error) {
|
||||
ctx := r.Context()
|
||||
user := auth.ContextUser(ctx)
|
||||
|
||||
if err := quizForm.Handle(r); err != nil {
|
||||
player, err := h.store.UpsertPlayer(ctx, user.Name, user.Email, user.Provider)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return player, nil
|
||||
}
|
||||
|
||||
func (h *Handler) handleSelectEntryForm(w http.ResponseWriter, r *http.Request) {
|
||||
player, err := h.getRequestPlayer(r)
|
||||
if err != nil {
|
||||
h.handleError(w, r, errors.WithStack(err))
|
||||
return
|
||||
}
|
||||
|
||||
selectEntryForm := component.NewSelectEntryForm()
|
||||
|
||||
if err := selectEntryForm.Handle(r); err != nil {
|
||||
h.handleError(w, r, errors.WithStack(err))
|
||||
return
|
||||
}
|
||||
@ -82,15 +96,166 @@ func (h *Handler) handleQuizForm(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if errs := quizForm.Validate(); errs != nil {
|
||||
if errs := selectEntryForm.Validate(); errs != nil {
|
||||
page := component.QuizPage(*vmodel)
|
||||
templ.Handler(page).ServeHTTP(w, r)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
page := component.QuizPage(*vmodel)
|
||||
templ.Handler(page).ServeHTTP(w, r)
|
||||
rawSelectedEntry, exists := selectEntryForm.Field("entry").Get("value")
|
||||
if !exists {
|
||||
page := component.QuizPage(*vmodel)
|
||||
templ.Handler(page).ServeHTTP(w, r)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
selectedEntry, err := strconv.ParseUint(rawSelectedEntry.(string), 10, 64)
|
||||
if err != nil {
|
||||
h.handleError(w, r, errors.WithStack(err))
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
turn, err := h.store.GetQuizTurn(ctx, h.playInterval, h.playPeriod)
|
||||
if err != nil {
|
||||
h.handleError(w, r, errors.WithStack(err))
|
||||
return
|
||||
}
|
||||
|
||||
err = h.store.Do(ctx, func(db *gorm.DB) error {
|
||||
err := db.Model(&player).Updates(map[string]any{
|
||||
"selected_at": time.Now().UTC(),
|
||||
"selected_entry": &selectedEntry,
|
||||
"selected_turn": turn.ID,
|
||||
}).Error
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
h.handleError(w, r, errors.WithStack(err))
|
||||
return
|
||||
}
|
||||
|
||||
baseURL := httpCtx.BaseURL(ctx)
|
||||
|
||||
http.Redirect(w, r, baseURL.String(), http.StatusTemporaryRedirect)
|
||||
}
|
||||
|
||||
func (h *Handler) handleAnswerForm(w http.ResponseWriter, r *http.Request) {
|
||||
player, err := h.getRequestPlayer(r)
|
||||
if err != nil {
|
||||
h.handleError(w, r, errors.WithStack(err))
|
||||
return
|
||||
}
|
||||
|
||||
if player.SelectedEntry == nil {
|
||||
h.handleError(w, r, errors.New("missing player selected entry"))
|
||||
return
|
||||
}
|
||||
|
||||
answerForm := component.NewAnswerForm()
|
||||
|
||||
if err := answerForm.Handle(r); err != nil {
|
||||
h.handleError(w, r, errors.WithStack(err))
|
||||
return
|
||||
}
|
||||
|
||||
vmodel, err := h.fillQuizPageVModel(r)
|
||||
if err != nil {
|
||||
h.handleError(w, r, errors.WithStack(err))
|
||||
return
|
||||
}
|
||||
|
||||
if errs := answerForm.Validate(); errs != nil {
|
||||
page := component.QuizPage(*vmodel)
|
||||
templ.Handler(page).ServeHTTP(w, r)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
rawAnswerIndex, exists := answerForm.Field("answer").Get("value")
|
||||
if !exists {
|
||||
page := component.QuizPage(*vmodel)
|
||||
templ.Handler(page).ServeHTTP(w, r)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
selectedAnswerIndex, err := strconv.ParseInt(rawAnswerIndex.(string), 10, 64)
|
||||
if err != nil {
|
||||
h.handleError(w, r, errors.WithStack(err))
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
turn, err := h.store.GetQuizTurn(ctx, h.playInterval, h.playPeriod)
|
||||
if err != nil {
|
||||
h.handleError(w, r, errors.WithStack(err))
|
||||
return
|
||||
}
|
||||
|
||||
var selectedEntry *store.QuizEntry
|
||||
for _, e := range turn.Entries {
|
||||
if *player.SelectedEntry != e.ID {
|
||||
continue
|
||||
}
|
||||
|
||||
selectedEntry = e
|
||||
break
|
||||
}
|
||||
|
||||
if selectedEntry == nil {
|
||||
h.handleError(w, r, errors.Errorf("invalid selected entry '%d'", *player.SelectedEntry))
|
||||
return
|
||||
}
|
||||
|
||||
if selectedAnswerIndex < 0 || int(selectedAnswerIndex) >= len(selectedEntry.Propositions) {
|
||||
h.handleError(w, r, errors.Errorf("invalid selected answer index '%d'", selectedAnswerIndex))
|
||||
return
|
||||
}
|
||||
|
||||
selectedAnswer := selectedEntry.Propositions[selectedAnswerIndex]
|
||||
|
||||
won := selectedAnswer == selectedEntry.Answer
|
||||
|
||||
err = h.store.Tx(ctx, func(db *gorm.DB) error {
|
||||
newScore := player.Score
|
||||
if won {
|
||||
newScore += 2 * int(selectedEntry.Level+1)
|
||||
}
|
||||
|
||||
result := db.Model(&player).Updates(map[string]any{
|
||||
"score": newScore,
|
||||
"selected_answer": selectedAnswerIndex,
|
||||
"played_at": time.Now().UTC(),
|
||||
}).Where("played_at = ?", player.PlayedAt)
|
||||
|
||||
if result.Error != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if result.RowsAffected != 1 {
|
||||
return errors.New("unexpected number of updated player")
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
h.handleError(w, r, errors.WithStack(err))
|
||||
return
|
||||
}
|
||||
|
||||
baseURL := httpCtx.BaseURL(ctx)
|
||||
baseURL = baseURL.JoinPath("/quiz/result")
|
||||
|
||||
http.Redirect(w, r, baseURL.String(), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func (h *Handler) handleError(w http.ResponseWriter, r *http.Request, err error) {
|
||||
|
91
internal/http/handler/webui/quiz/result_page.go
Normal file
91
internal/http/handler/webui/quiz/result_page.go
Normal file
@ -0,0 +1,91 @@
|
||||
package quiz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
httpCtx "forge.cadoles.com/wpetit/kouiz/internal/http/context"
|
||||
"forge.cadoles.com/wpetit/kouiz/internal/http/handler/webui/common"
|
||||
"forge.cadoles.com/wpetit/kouiz/internal/http/handler/webui/quiz/component"
|
||||
"forge.cadoles.com/wpetit/kouiz/internal/store"
|
||||
"github.com/a-h/templ"
|
||||
"github.com/pkg/errors"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func (h *Handler) getResultPage(w http.ResponseWriter, r *http.Request) {
|
||||
vmodel, err := h.fillResultPageVModel(r)
|
||||
if err != nil {
|
||||
h.handleError(w, r, errors.WithStack(err))
|
||||
return
|
||||
}
|
||||
|
||||
if vmodel.Entry == nil {
|
||||
baseURL := httpCtx.BaseURL(r.Context())
|
||||
http.Redirect(w, r, baseURL.String(), http.StatusTemporaryRedirect)
|
||||
return
|
||||
}
|
||||
|
||||
resultPage := component.ResultPage(*vmodel)
|
||||
templ.Handler(resultPage).ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func (h *Handler) fillResultPageVModel(r *http.Request) (*component.ResultPageVModel, error) {
|
||||
vmodel := &component.ResultPageVModel{}
|
||||
|
||||
err := common.FillViewModel(
|
||||
r.Context(), vmodel, r,
|
||||
h.fillResultPagePlayer,
|
||||
h.fillResultPageSelectedEntry,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return vmodel, nil
|
||||
}
|
||||
|
||||
func (h *Handler) fillResultPagePlayer(ctx context.Context, vmodel *component.ResultPageVModel, r *http.Request) error {
|
||||
player, err := h.getRequestPlayer(r)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
vmodel.Player = player
|
||||
|
||||
rank, err := h.store.GetPlayerRank(ctx, player.ID)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
vmodel.PlayerRank = rank
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Handler) fillResultPageSelectedEntry(ctx context.Context, vmodel *component.ResultPageVModel, r *http.Request) error {
|
||||
if vmodel.Player == nil || vmodel.Player.SelectedEntry == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var entry *store.QuizEntry
|
||||
err := h.store.Do(ctx, func(db *gorm.DB) error {
|
||||
err := db.Model(&entry).Preload("Category").First(&entry, "id = ?", vmodel.Player.SelectedEntry).Error
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
vmodel.Entry = entry
|
||||
|
||||
if vmodel.Player.SelectedAnswer != nil && *vmodel.Player.SelectedAnswer >= 0 && *vmodel.Player.SelectedAnswer < len(entry.Propositions) {
|
||||
selectedAnswer := entry.Propositions[*vmodel.Player.SelectedAnswer]
|
||||
vmodel.Won = selectedAnswer == entry.Answer
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Reference in New Issue
Block a user