Files
kouiz/internal/http/handler/webui/quiz/component/quiz_page.templ
2025-06-15 14:46:32 +02:00

170 lines
4.9 KiB
Plaintext

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
CurrentTurn *store.QuizTurn
}
func NewSelectEntryForm() *form.Form {
return form.New(
form.NewField(
"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."),
),
)
}
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)
}
}
func markdownToHTML(ctx context.Context, text string) string {
var buff bytes.Buffer
if err := goldmark.Convert([]byte(text), &buff); err != nil {
slog.ErrorContext(ctx, "could not convert markdown to html", slog.Any("error", errors.WithStack(err)))
return ""
}
return buff.String()
}