feat: initial commit
This commit is contained in:
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user