feat: initial commit

This commit is contained in:
2025-06-15 14:46:32 +02:00
parent 85f0bc1024
commit c50d1858f2
25 changed files with 2002 additions and 55 deletions

View File

@ -1,8 +1,13 @@
package config
import "time"
import (
"time"
"forge.cadoles.com/wpetit/kouiz/internal/timex"
)
type Quiz struct {
Language string `env:"LANGUAGE" envDefault:"fr"`
PlayInterval time.Duration `env:"PLAY_INTERVAL" envDefault:"2h"`
Language string `env:"LANGUAGE" envDefault:"fr"`
PlayInterval time.Duration `env:"PLAY_INTERVAL" envDefault:"2h"`
PlayPeriod timex.PeriodType `env:"PLAY_PERIOD" envDefault:"daily"`
}

View File

@ -18,7 +18,6 @@ templ LoginPage(vmodel LoginPageVModel) {
<nav class="panel is-link" style="min-width: 33%">
<p class="panel-heading">
<span class="title">Kouiz</span>
<span>&nbsp;- Choisissez votre plateforme</span>
</p>
for _, provider := range vmodel.Providers {
<a class="panel-block py-5" href={ templ.URL("/auth/providers/" + provider.ID) } hx-boost="false">

View File

@ -53,7 +53,7 @@ func LoginPage(vmodel LoginPageVModel) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"is-flex is-justify-content-center is-align-items-center is-fullheight\"><nav class=\"panel is-link\" style=\"min-width: 33%\"><p class=\"panel-heading\"><span class=\"title\">Kouiz</span> <span>&nbsp;- Choisissez votre plateforme</span></p>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"is-flex is-justify-content-center is-align-items-center is-fullheight\"><nav class=\"panel is-link\" style=\"min-width: 33%\"><p class=\"panel-heading\"><span class=\"title\">Kouiz</span></p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -96,7 +96,7 @@ func LoginPage(vmodel LoginPageVModel) templ.Component {
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(provider.Label)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/auth/component/login_page.templ`, Line: 28, Col: 46}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/auth/component/login_page.templ`, Line: 27, Col: 46}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {

View File

@ -31,13 +31,25 @@ func (h *Handler) handleProviderCallback(w http.ResponseWriter, r *http.Request)
slog.DebugContext(ctx, "authenticated user", slog.Any("user", gothUser))
user := &User{
ID: gothUser.UserID,
Email: gothUser.Email,
Provider: gothUser.Provider,
AccessToken: gothUser.AccessToken,
IDToken: gothUser.IDToken,
Name: gothUser.Name,
}
if user.Email == "" {
slog.ErrorContext(r.Context(), "could not authenticate user", slog.Any("error", errors.New("user email missing")))
http.Redirect(w, r, "/auth/logout", http.StatusTemporaryRedirect)
return
}
if user.Provider == "" {
slog.ErrorContext(r.Context(), "could not authenticate user", slog.Any("error", errors.New("user provider missing")))
http.Redirect(w, r, "/auth/logout", http.StatusTemporaryRedirect)
return
}
rawPreferredUsername, exists := gothUser.RawData["preferred_username"]
if exists {
if preferredUsername, ok := rawPreferredUsername.(string); ok {

View File

@ -1,7 +1,7 @@
package auth
type User struct {
ID string
Email string
Provider string
AccessToken string
IDToken string

View File

@ -0,0 +1,326 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
style="display: block;"
viewBox="0 0 2048 2048"
width="1024"
height="1024"
id="svg69"
sodipodi:docname="panda.svg"
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview69"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="true"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="0.55761719"
inkscape:cx="557.7303"
inkscape:cy="397.22592"
inkscape:window-width="1440"
inkscape:window-height="831"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg69" />
<defs
id="defs2">
<linearGradient
id="Gradient1"
gradientUnits="userSpaceOnUse"
x1="924.34"
y1="1063.37"
x2="867.171"
y2="972.986"
gradientTransform="matrix(-1,0,0,1,2049.9982,0)">
<stop
class="stop0"
offset="0"
stop-opacity="1"
stop-color="rgb(209,95,95)"
id="stop1" />
<stop
class="stop1"
offset="1"
stop-opacity="1"
stop-color="rgb(226,131,129)"
id="stop2" />
</linearGradient>
</defs>
<path
fill="#000000"
d="m 694.36821,79.4274 c -12.41,-0.7929 -25.77,1.1135 -37.66,4.6791 -28.54,8.5547 -54.02,29.8815 -68.13,56.0855 -13.21,24.54 -12.65,50.57 -4.66,76.626 -16.52,10.092 -29.84,21.473 -43.5,35.046 3.32,-16.684 3.89,-33.8 1.69,-50.669 -64.46,28.494 -105.95,82.497 -132.22,146.254 -7.64,18.556 -13.02,38.114 -21.31,56.391 -6.22,13.707 -14.57,26.905 -22.01,40.005 l -3.56,-2.892 c -37.46,-30.121 -83.77,-49.505 -132.45,-43.982 -34.09,3.868 -65.37,24.002 -86.43,50.651 -25.86,32.719 -34.35,78.312 -29.62,119.034 4.39,37.695 22.42,74.397 52.65,97.937 42.46,33.054 95.6,39.181 147.51,32.92 -0.53,58.807 7.05,119.399 32.7,173.034 26.98,56.435 84.3,91.069 141.58,111.299 16.3,22.694 34.87,43.694 51.01,66.404 -13.84,31.77 -24.55,64.92 -37.67,97 -37.01,90.45 -82.41,182.05 -142.86,259.26 4.31,9 5.6,17.67 7.24,27.4 -36.61,-6.87 -88.73,-29.18 -125.49,-17.18 -6.21,2.03 -13,5.86 -15.84,12.05 -2.12,4.64 -2.36,9.66 -0.5,14.4 10.64,27.1 42.87,40.93 67.81,51.4 -3.95,-0.23 -8.28,-0.31 -12.21,0.21 -4.83,0.65 -10.42,3.86 -13.2,7.88 -2.55,3.69 -3.16,8 -2.22,12.34 1.85,8.5 8.58,14.85 15.78,19.16 34.53,20.67 78.08,12.32 114.63,3.04 -7.89,13.68 -20.33,24.44 -27.58,38.28 -5.55,10.61 -7.29,23.7 -3.24,35.12 2.9,8.17 10.47,17.52 18.63,20.87 3.83,1.58 7.9,1.41 11.66,-0.29 11.39,-5.16 16.88,-23.67 25.61,-32.65 14.36,-14.78 34.42,-22.74 48.2,-38.07 7.79,-8.67 14.02,-19.56 18.03,-30.46 19.17,2.35 33.44,1.02 51.74,-5.16 -3.24,36.42 -11.44,73.1 -20.4,108.49 -14.82,60.55 -34.56,119.78 -59.02,177.11 -10.96,25.58 -23.48,50.27 -35.42,75.38 23.04,14.94 47.47,25.25 73.1,34.83 125.21,46.76 304.09,60.04 435.73,38.79 23.74,-3.83 50.75,-7.68 72.69999,-17.72 -9.85999,-10.71 -21.07999,-20.08 -31.67999,-30.04 -10.35,-9.72 -20.27,-19.96 -30.29,-30.02 111.64499,14.03 267.58899,5.32 374.86399,-26.59 -20.493,-30.69 -46.037,-57.7 -75.548,-79.86 -27.775,-20.34 -55.893,-35.28 -90.847,-36.59 24.791,-69.33 53.287,-137.27 85.365,-203.55 7.741,-15.65 14.901,-33.4 24.764,-47.71 4.922,-7.15 11.846,-12.87 17.138,-19.78 11.954,-15.62 18.085,-37.64 22.452,-56.53 16.857,-72.95 14.135,-148.02 3.829,-221.68 80.07,-10.68 158.998,-38.6 231.644,-73.25 20.604,-9.83 41.78,-20.08 60.631,-33.03 15.289,-10.5 27.88,-23.24 40.919,-36.3 31.062,2.49 58.66,0.46 87.919,-10.86 21.89,15.42 42.345,33.53 63.195,50.34 9.348,7.54 19.519,17.39 30.511,22.34 8.879,3.99 17.682,0.74 26.269,-2.32 11.693,9.65 47.129,45.33 60.74,44.89 6.643,-0.21 12.719,-4.36 17.186,-8.99 6.293,-6.53 12.889,-16.48 12.49,-25.96 -0.121,-2.86 -0.869,-5.44 -2.5,-7.81 -6.527,-9.47 -39.82,-38.38 -50.249,-47.31 3.119,-6.91 6.695,-13.81 5.571,-21.64 -0.627,-4.37 -2.286,-8.11 -4.872,-11.66 -15.99,-21.91 -79.133,-74.843 -101.67,-92.093 7.34,-11.025 13.693,-22.836 14.297,-36.379 0.775,-17.355 -8.221,-36.731 -19.685,-49.469 -9.483,-10.536 -20.994,-17.298 -35.304,-18.075 -0.575,-3.993 -1.135,-8.007 -1.912,-11.966 -2.478,-12.622 -9.406,-24.578 -20.279,-31.713 -10.099,-6.628 -24.298,-9.293 -36.113,-6.726 -13.038,2.833 -17.446,12.018 -24.203,22.172 -20.323,-17.62 -41.099,-34.735 -61.012,-52.815 1.969,-3.898 3.99,-8.04 5.357,-12.193 2.027,-6.158 3.048,-12.101 1.759,-18.529 -3.259,-16.252 -32.517,-63.493 -41.913,-82.726 4.775,-12.392 10.808,-26.405 8.267,-39.998 -1.487,-7.957 -5.763,-14.866 -12.501,-19.418 -15.103,-10.203 -36.168,-7.565 -52.856,-3.878 -27.295,-19.916 -59.785,-28.236 -93.333,-25.632 -39.024,3.121 -75.153,21.802 -100.259,51.84 -24.508,29.141 -33.996,67.712 -30.493,105.229 3.055,32.718 20.748,65.86 46.028,86.83 0.448,14.383 3.344,30.459 11.131,42.798 4.711,7.464 11.383,13.138 20.173,14.954 15.14,3.128 29.101,-4.088 41.097,-12.402 7.773,2.837 15.589,5.558 23.444,8.161 12.385,4.108 24.888,7.684 37.444,11.228 21.96,6.199 38.134,12.707 60.037,1.718 3.94,3.263 7.207,6.971 10.585,10.787 -2.894,1.766 -5.792,3.561 -8.191,5.987 -5.172,5.229 -5.31,11.316 -5.377,18.191 -23.599,7.365 -47.015,14.614 -71.403,18.917 -77.254,13.63 -178.184,9.447 -249.873,-24.345 -6.947,-13.011 -13.507,-21.149 -25.137,-30.26 4.763,-15.762 9.383,-31.567 13.861,-47.412 26.734,-99.124 30.373,-203.06 10.638,-303.812 -5.394,-27.349 -12.305,-57.023 -26.253,-81.341 -38.543,-67.198 -175.01199,-123.305 -245.82199,-147.274 -26.9,-9.104 -54.27,-16.445 -81.69,-23.757 -0.47,-46.499 -10.2,-89.326 -41.72,-124.995 -28.3,-32.0214 -65.71,-45.4466 -107.42,-47.9966 z"
id="path3" />
<path
fill="#fefefe"
d="m 682.78821,203.681 c -2.36,0.096 -4.7,0.316 -7.04,0.659 -77.43,11.033 -137.19,71.466 -181.86,131.158 13.64,-33.808 25.46,-68.747 33.15,-104.405 -52.92,42.628 -77.84,78.887 -104.92,140.14 -14,31.677 -25.31,57.772 -44.74,86.859 15.03,14.529 29.96,29.788 43.1,46.064 -32.98,55.122 -45.82,109.913 -40.36,173.891 -14.98,6.204 -29.59,11.531 -45.36,15.42 l -0.02,1.488 c -0.98,68.312 14.56,155.158 63.93,205.269 75.31,76.458 303.51,111.866 408.72,121.826 36.34,3.22 72.8,4.99 109.28,5.32 -0.57,-5.85 -1.94,-12.23 1.13,-17.55 9.8,-6.78 33.81,-1.3 45.21,0.46 10.82,1.79 21.75,2.96 32.61,4.46 18.73999,2.58 34.12999,6.27 52.88999,1.61 10.572,-2.62 17.743,-9.06 28.71,-10.61 18.208,-24.63 35.218,-49.645 50.975,-75.905 -6.694,-6.968 -13.3,-14.021 -19.816,-21.156 7.33,-2.86 15.054,-4.921 22.586,-7.205 1.489,-2.238 2.859,-4.554 4.103,-6.938 12.867,-24.966 20.567,-54.68 27.58,-81.8 29.459,-113.92 37.86,-241.959 0,-355.035 -3.237,-9.667 -6.777,-20.167 -12.44,-28.732 -22.643,-34.245 -79.012,-66.188 -115.618,-84.365 -59.51999,-29.217 -121.94999,-52.09 -186.25999,-68.244 -15.51,-3.885 -33.22,-8.938 -49.24,-8.68 -12.31,0.198 -24.58,4.984 -36.14,8.859 16.06,7.39 34.26,10.813 49.37,20.172 -25.31,3.002 -50.33,2.579 -75.57,7.001 7.17,-7.083 15.87,-12.101 24.35,-17.418 -24.2,-2.758 -45.91,1.842 -68.63,9.979 22.72,-20.442 51.24,-35.354 80.48,-43.77 -43.45,-5.56 -89.65,-0.875 -129.88,16.656 -10.8,4.704 -20.75,10.924 -31.15,16.392 12.37,-15.037 25.95,-29.318 38.85,-43.919 10.97,-12.414 21.37,-25.267 32.02,-37.951 z"
id="path4" />
<path
fill="#000000"
d="m 1013.1982,512.944 c 11.51,-7.778 21.74,-15.564 29.93,-26.945 12.139,-16.867 17.043,-37.077 13.552,-57.593 -4.699,-27.62 -25.712,-47.973 -47.842,-63.331 -48.72999,-33.824 -136.80999,-61.516 -195.72999,-50.397 -16.96,3.202 -36.64,11.159 -46.37,26.248 -6.16,9.561 -7.53,21.877 -4.83,32.817 9.76,39.691 67.99,84.189 101.6,105.185 25.32,15.809 78.83,45.999 108.6,38.872 0.36,-0.088 0.73,-0.184 1.09,-0.277 l 1.29,1.835 c -31.48,11.07 -63.83,22.832 -84.93,50.456 -4.1,5.361 -10.09,12.803 -9.16,19.816 4.68,6.409 15.12,11.679 22.22,15.06 30.27,14.418 61.75,15.903 93.16,4.911 7.97999,18.083 14.69999,45.881 8.56999,65.253 -6.14999,19.46 -29.05999,33.934 -46.26999,42.644 -44.32,22.421 -81.04,30.709 -129.53,14.768 2.79,-3.319 7.2,-7.586 8.51,-11.745 0.73,-2.292 1.18,-4.595 0.03,-6.801 l -2.28,-0.254 c -10.94,4.804 -17.45,16.151 -26.19,23.771 -4.59,3.995 -11.15,5.364 -14.96,10.18 l 0.5,1.098 c 1.83,0.939 3.19,1.618 5.28,1.88 7.76,0.976 16.39,-5.613 22.34,-9.859 2.43,1.451 4.89,2.86 7.5,3.951 31.21,13 73.25,8.273 103.72,-4.46 31.93,-13.343 67.09999,-31.575 80.83999,-64.984 3.94,-0.235 10.84,-1.801 13.9,0.648 0.72,6.196 -0.68,11.792 -3.62,17.268 -18.49,34.381 -81.98999,63.347 -118.82999,74.779 -24.12,7.486 -55.05,10.841 -79.36,2.113 -6.01,-2.158 -12.02,-6.489 -17.7,-9.51 4.72,7.926 9.74,12.912 17.16,18.338 7.58,37.821 33.82,73.532 64.22,96.584 30.9,23.427 72.7,35.398 111.29999,29.973 15.41,-2.166 40.6,-8.967 49.948,-22.541 16.333,-23.722 -18.178,-46.421 -21.818,-63.29 1.76,-0.96 0.93,-0.921 2.82,-0.344 12.93,3.941 28.327,17.189 34.358,29.09 6.122,12.081 4.828,23.014 0.575,35.335 l 0.514,0.901 c 0.637,-0.21 1.303,-0.346 1.911,-0.63 3.43,-1.602 7.423,-8.455 8.522,-11.932 4.042,-12.796 -4.652,-26.182 -10.302,-37.176 l 1.378,0.864 c 2.181,1.339 4.401,2.631 6.45,4.172 9.942,7.478 18.834,19.432 20.503,31.903 1.455,10.873 -2.822,20.888 -9.411,29.327 -22.813,29.215 -62.258,31.923 -95.99799,35.559 18.68999,5.811 38.72999,5.637 57.31999,-0.499 19.983,-6.61 41.783,-19.862 51.204,-39.476 4.505,-9.379 6.96,-20.175 4.145,-30.414 -5.391,-19.603 -42.854,-53.719 -55.819,-75.054 -13.81,-22.725 -22.24,-48.557 -30.77,-73.598 16.15,-17.907 16.91,-24.916 20.03,-47.39 4.94,-5.529 11.29,-10.207 16.67,-15.348 14.173,-13.541 24.04,-30.403 25.561,-50.19 1.887,-24.557 -7.681,-51.357 -23.594,-69.97 -11.807,-13.812 -23.687,-20.12 -41.907,-21.591 z"
id="path5" />
<path
fill="#3d3d3d"
d="m 974.48821,519.358 c 7.24,-0.874 15.09,-2.905 22.07,-5.092 20.79999,-6.513 36.24999,-22.581 46.08999,-41.667 8.177,-15.863 10.836,-34.528 5.15,-51.669 -9.73,-29.347 -39.22,-50.032 -65.60999,-63.267 -46.63,-23.383 -129.95,-47.87 -180.07,-30.722 -11.26,3.851 -22.93,10.683 -28.03,21.898 -3.95,8.679 -4.41,19.396 -1.05,28.334 15.06,40.043 75.49,87.922 114.41,105.581 l 0.41,-0.561 -3.53,-2.426 c 1.86,-1.318 2.82,-0.558 4.99,-0.215 0.76,-1.934 -9.8,-10.51 -11.76,-12.233 -27.37,-24.154 -60.75,-58.856 -62.74,-97.573 -0.5,-9.71 1.64,-18.62 8.38,-25.891 8.38,-9.041 20.85,-12.499 32.81,-13.092 36.17,-1.792 77.52,14.457 104.12,38.691 24.08,21.94 45.48999,56.264 46.89999,89.581 0.75,17.857 -6.15,36.542 -19.77999,48.524 -4.33,3.807 -10.14,5.732 -14.05,9.964 z"
id="path6" />
<path
fill="#fefefe"
d="m 867.78821,341.353 c -9.48,-1.388 -22.67,-1.34 -31.27,3.233 -5.34,2.842 -9.04,7.606 -10.62,13.437 -4.88,17.949 5.95,40.671 14.51,55.921 23.42,41.77 66.3,79.857 112.81,93.011 1.01,0.224 2.01,0.453 3.03,0.644 7.97,1.501 15.6,-0.302 22.19,-5.104 10.26,-7.476 16.64,-19.52 18.2,-31.996 2.99,-23.889 -11.46,-49.329 -25.49,-67.536 -24.83,-32.201 -62.89,-56.304 -103.36,-61.61 z"
id="path7" />
<path
fill="#000000"
d="m 885.04821,410.189 c -2.73,-0.239 -5.75,-0.247 -8.34,0.699 -3.95,1.442 -7.91,3.832 -9.63,7.822 -3.15,7.307 1.05,18.663 4.41,25.19 9.7,18.864 30.09,34.815 50.18,41.049 5.04,0.805 8.59,0.949 13.24,-1.448 3.13,-1.612 5.24,-3.975 6.17,-7.395 2.64,-9.624 -1.08,-21.668 -6.01,-29.943 -10.47,-17.565 -30.37,-31.165 -50.02,-35.974 z"
id="path8" />
<path
fill="#fefefe"
d="m 886.99821,430.271 c -3.45,0.201 -7.21,0.38 -9.89,2.921 -1.77,1.69 -2.67,4.347 -2.53,6.77 0.22,3.788 2.6,7.418 5.23,10.001 4.13,4.051 9.65,4.387 15.12,4.427 3.26,-0.942 6.82,-1.936 9.64,-3.901 2.3,-1.605 3.18,-3.439 3.81,-6.124 -0.29,-2.128 -0.56,-3.328 -1.82,-5.135 -4.14,-5.928 -12.89,-7.942 -19.56,-8.959 z"
id="path9" />
<path
fill="#724a4a"
d="m 1008.9182,722.63 c -3.22,3.093 -6.77,5.896 -10.18999,8.772 -48.52,32.364 -106.89,59.838 -166.51,50.008 1.52,4.51 2.79,9.04 5.35,13.084 9.48,21.926 20.53,39.695 37.6,56.41 l 0.89,-2.034 c -1.67,-1.471 -3.06,-3.635 -4.46,-5.381 0.71,-1.342 0.73,-1.486 1.77,-2.857 9.69,-12.776 40,-17.678 55.02,-18.617 31.44,-1.965 66.14,6.938 92.40999,24.432 10.27,6.838 19.55,15.289 28.81,23.411 -4.63,-18.277 -27.57,-41.058 -27.52,-53.161 0.82,-1.532 0.95,-2.55 2.62,-3.326 4.33,-2.022 9.16,0.014 13.42,1.33 8.14,4.878 18.139,14.53 27.38,16.284 l 0.399,-0.944 c -14.77,-20.342 -29.369,-38.936 -39.629,-62.246 -6.51,-14.777 -10.66,-30.444 -17.36,-45.165 z"
id="path10" />
<path
fill="#c76362"
d="m 1008.9182,722.63 c -3.22,3.093 -6.77,5.896 -10.18999,8.772 4.54999,25.146 15.73999,46.616 29.19999,68.03 3.23,5.141 7.25,10.024 10.2,15.269 8.14,4.878 18.139,14.53 27.38,16.284 l 0.399,-0.944 c -14.77,-20.342 -29.369,-38.936 -39.629,-62.246 -6.51,-14.777 -10.66,-30.444 -17.36,-45.165 z"
id="path11" />
<path
fill="#c76362"
d="m 871.59821,843.489 0.16,-3.288 c -4.41,-6.039 -29.78,-43.719 -31.88,-45.161 -0.65,-0.448 -1.54,-0.364 -2.31,-0.546 9.48,21.926 20.53,39.695 37.6,56.41 l 0.89,-2.034 c -1.67,-1.471 -3.06,-3.635 -4.46,-5.381 z"
id="path12" />
<path
fill="#e49895"
d="m 875.16821,850.904 c 6.98,9.796 18.56,18.125 28.75,24.297 31.35,18.988 69.43,27.625 105.50999,18.716 10.36,-2.558 25.77,-6.95 31.52,-16.53 0.21,-2.607 0.18,-3.552 -1.45,-5.667 -3.52,-4.545 -9.36,-8.072 -14.03,-11.368 -22.7,-16.039 -45.91999,-26.919 -73.74999,-30.047 -26.88,-3.02 -54.12,1.538 -75.66,18.565 z"
id="path13" />
<path
fill="#fefefe"
d="m 1020.2882,518.411 c -10.4,-0.47 -20.42999,0.514 -30.66999,2.304 6.48,1.848 14.66999,3.215 19.28999,8.546 7.79,8.996 7.77,27.009 7,38.347 -0.96,13.961 -4.39,29.131 -15.36,38.616 11.38,18.911 14.49,38.673 14.46,60.402 3.48,-0.961 6.9,-2.349 10.31,-3.545 2.61,-1.288 5.16,-2.699 7.64,-4.227 17.159,-10.542 32.744,-28.813 37.006,-48.853 4.496,-21.141 -2.6,-44.836 -14.106,-62.61 -7.82,-12.08 -21.01,-25.805 -35.57,-28.98 z"
id="path14" />
<path
fill="#3d3d3d"
d="m 900.38821,592.25 c 12.01,-0.325 23.2,-1.617 34.81,-4.787 20.69,-5.647 52.35,-19.145 62.92,-39.014 2.10999,-3.976 3.52999,-8.328 2.23999,-12.767 -0.86999,-1.098 -1.63999,-1.956 -2.97999,-2.486 -5.78,-2.288 -16.15,-2.92 -21.83,-0.461 -33.42,5.935 -69.42,21.956 -89.69,50.301 -1.05,1.464 -1.94,2.926 -2.85,4.48 6.04,3.126 10.42,4.962 17.38,4.734 z"
id="path15" />
<path
fill="#fefefe"
d="m 975.54821,532.735 c -33.42,5.935 -69.42,21.956 -89.69,50.301 -1.05,1.464 -1.94,2.926 -2.85,4.48 6.04,3.126 10.42,4.962 17.38,4.734 9.07,-18.496 24.88,-33.527 42.63,-43.797 6.68,-3.869 29.63,-11.056 32.53,-15.718 z"
id="path16" />
<path
fill="#000000"
d="m 664.34821,421.401 c -10.71,-0.861 -20.97,0.054 -31.34,2.948 -33.75,9.43 -59.92,34.124 -76.74,64.204 -32.76,58.573 -30.01,130.582 -11.89,193.392 9.51,32.988 26.79,84.832 58.15,101.908 27.61,15.036 56.07,13.087 85.43,4.451 21.29,-8.074 39.84,-18.154 57.58,-32.48 42.71,-34.485 77.49,-88.723 83.19,-144.301 4.5,-43.991 -14.49,-82.084 -41.7,-115.462 -31.65,-38.821 -71.65,-69.379 -122.68,-74.66 z"
id="path17" />
<path
fill="#3d3d3d"
d="m 669.46821,428.321 c -13.36,-1.272 -26.15,-0.588 -39.02,3.479 -29.91,9.455 -55.85,33.721 -70.17,61.261 -32.86,63.177 -21.86,141.423 -1.04,206.479 8.84,27.619 21.25,66.004 49.18,79.717 23.62,11.596 55.34,6.669 79.32,-1.082 16.09,-6.025 31.28,-14.201 45.17,-24.307 42.08,-30.246 82.72,-89 90.82,-140.72 5.57,-35.543 -14.96,-75.379 -35.16,-103.217 -28.17,-38.832 -70.56,-73.918 -119.1,-81.61 z"
id="path18" />
<path
fill="#000000"
d="m 671.42821,466.413 c -4.75,-0.316 -9.52,-0.259 -14.26,0.172 -19.9,1.669 -37.13,12.441 -49.84,27.465 -17.88,21.133 -24.42,44.876 -22.12,72.279 3.68,43.587 28.45,86.179 61.74,113.951 22.32,18.614 45.3,27.192 74.28,24.501 17.28,-2.244 34.43,-9.419 47.87,-20.562 22.94,-19.015 36.48,-49.054 39.01,-78.447 2.41,-28.069 -6.86,-54.288 -25.08,-75.517 -28.16,-32.818 -67.53,-60.444 -111.6,-63.842 z"
id="path19" />
<path
fill="#fefefe"
d="m 675.32821,477.352 c -4.84,-0.43 -9.71,-0.551 -14.58,-0.362 -16.89,0.589 -32.84,9.548 -44.09,21.835 -14.78,16.322 -22.35,37.915 -21,59.893 2.1,39.03 27.8,82.621 56.61,108.085 19.03,16.829 40.35,26.36 65.98,24.694 16.48,-1.685 30.97,-7.519 44.19,-17.569 20.55,-15.629 36.68,-41.04 40.09,-66.823 3.26,-24.684 -5.83,-46.432 -20.71,-65.734 -25.43,-32.989 -64.88,-58.568 -106.49,-64.019 z"
id="path20" />
<path
fill="#000000"
d="m 739.38821,546.111 c -6.07,-0.521 -10.68,-0.516 -15.92,2.966 -6.24,4.143 -9.1,10.722 -10.56,17.83 -3.06,15 -0.23,30.181 8.36,42.885 8.24,12.189 19.04,19.421 33.49,22.086 4.58,0.273 9.12,-1.37 13.16,-3.385 5.86,-2.922 9.66,-7.398 11.71,-13.615 4.3,-13.066 1.41,-27.397 -4.86,-39.296 -7.89,-14.962 -19.24,-24.345 -35.38,-29.471 z"
id="path21" />
<path
fill="#fefefe"
d="m 739.70821,560.355 c -2.67,-0.028 -6.15,-0.357 -8.68,0.562 -3.83,1.388 -9.03,5.646 -10.47,9.566 -0.81,2.204 -1,4.099 -0.05,6.296 2.96,6.807 16.18,9.796 22.84,12.388 3.76,1.02 8.39,1.225 11.84,-0.984 2.39,-1.536 3.87,-3.582 4.44,-6.368 0.83,-4.084 -0.65,-8.311 -2.91,-11.682 -4.06,-6.051 -10.14,-8.369 -17.01,-9.778 z"
id="path22" />
<path
fill="#000000"
d="m 535.87821,367.124 c -9.55,-0.232 -17.12,1.923 -24.22,8.573 -11.06,10.36 -15.13,26.957 -15.78,41.633 -0.54,12.186 1.23,39.616 9.72,48.994 5.85,-19.922 8.1,-41.268 19.75,-59.21 5.8,-8.929 15.15,-16.489 20.15,-25.613 1.26,-2.313 1.59,-5.003 0.44,-7.46 -1.82,-3.892 -6.37,-5.439 -10.06,-6.917 z"
id="path23" />
<path
fill="#3d3d3d"
d="m 860.34821,1157.25 c -23.87,-14.11 -60.72,-23.71 -87.49,-32.42 -8.72,-2.85 -18.73,-5.19 -26.87,-9.42 -10.03,-5.21 -18.43,-16.09 -26.49,-23.93 -8.9,-8.8 -18.3,-17.08 -28.17,-24.79 -61.5,-47.31 -103.97,-52.68 -173.68,-75.047 14.1,18.257 31.43,36.337 43.09,56.137 7.48,-2.01 18.86,-5.02 26.55,-3.53 2.07,0.4 1.33,0 2.57,1.73 -0.86,2.06 -0.76,2.03 -2.78,2.95 -10.02,2.53 -20.22,5.38 -29.04,10.96 -6,14.81 -10.42,30.39 -15.43,45.56 -5.69,17.18 -11.87,34.2 -18.55,51.03 -36.26,91.69 -83.81,178.5 -141.56,258.42 2.35,2.19 4.89,4.31 6.9,6.8 -1.44,1.69 -2.03,2.71 -2.56,4.87 3.75,12.45 14.78,24.39 23.69,33.56 24.68,25.38 60.4,47.06 96.74,47.27 6.9,0.04 14.69,-0.52 21.15,-3.04 5.74,-2.25 10.8,-6.51 16.06,-9.69 66.63,-40.27 125.58,-105.19 177.83,-162.46 9.66,-10.59 17.6,-22.84 27.88,-32.77 l 1.02,-0.96 c -42.34,70.14 -94.72,133.71 -155.47,188.69 26.11,26.55 53.37,49.2 86.15,67.16 108.57,59.51 239.54,61.78 356.51999,27.63 1.3,-4.51 2.5,-9.05 3.63,-13.61 16.072,-64.44 18.222,-140.44 8.535,-206.06 -1.605,-10.84 -2.775,-25.43 -8.785,-34.79 -25.15,-39.19 -106.20999,-76.6 -150.41999,-91.16 -19.98,-6.57 -40.46,-10.98 -61.06,-15.12 6.84,-23.35 17.58,-43.25 30.04,-63.97 z"
id="path24" />
<path
fill="#fefefe"
d="m 587.07821,1048.93 c -10.02,2.53 -20.22,5.38 -29.04,10.96 -6,14.81 -10.42,30.39 -15.43,45.56 -5.69,17.18 -11.87,34.2 -18.55,51.03 -36.26,91.69 -83.81,178.5 -141.56,258.42 2.35,2.19 4.89,4.31 6.9,6.8 l 1.51,-0.1 c 7.87,-7.74 18.1,-26.59 25.17,-36.51 56.36,-79.11 103.56,-164.35 140.71,-254.09 11.28,-27.08 22.13,-53.86 30.29,-82.07 z"
id="path25" />
<path
fill="#000000"
d="m 610.58821,1056.89 -1.51,0.38 c 1.43,2.81 11.36,6.39 14.52,8.23 11.32,6.48 22.12,13.82 32.3,21.97 47.77,37.89 87.22,90.29 109.15,147.02 l 0.55,0.89 c 0.91,-14.17 -9.49,-38.96 -15.4,-51.96 -25.85,-56.92 -81.81,-104.78 -139.61,-126.53 z"
id="path26" />
<path
fill="#3d3d3d"
d="m 1193.4292,1530.57 -1.353,0.61 c -11.944,5.47 -22.944,13.09 -34.368,19.55 -12.354,6.99 -25.574,13.06 -38.683,18.51 -23.668,9.84 -48.11,19.13 -72.767,26.15 -107.12999,30.52 -230.24999,33.56 -335.79999,-4.76 -37.58,-13.65 -72.32,-35.57 -102.35,-61.85 -9.86,-8.63 -18.97,-18.03 -28.68,-26.79 l -16.54,7.48 c -2.44,21.02 -4.51,42.14 -8.04,63.01 -14.65,86.44 -42.03,170.48 -75.84,251.2 -7.19,17.16 -13.22,35.29 -21.42,51.94 107.96,58.67 278.55,75.74 399.75,68.86 26.97,-1.53 53.65,-5.44 80.36,-9.24 -2.93,-5.41 -10.25,-13.24 -10.57,-19.02 5.18,5.78 9.53,12.07 13.55,18.69 7.96,-1.22 15.78,-3.28 23.6,-5.17 -10.33,-10.67 -21.54,-20.6 -32.39,-30.75 -24.19,-22.62 -49.54,-48.7 -80.57,-61.62 -48.9,-20.35 -126.66,8.19 -173.82,27.52 7.46,-1.79 14.92,-3.54 22.39,-5.24 -6.23,5.55 -55.02,17.64 -66.63,21.35 53.52,-37.8 129.24,-71.69 195.77,-68.21 13.17,-22.18 39.17,-55.32 61.49,-68.98 l 2.32,0.58 c -22.17,21.65 -44.55,39.98 -59.75,67.86 6.66,0.91 13.1,2.67 19.6,4.34 42.4,-59.17 96.6,-108.93 159.16999,-146.15 0.34,-0.09 0.69,-0.15 1.02,-0.26 2.87,-0.98 5.66,-3.16 8.2,-4.82 l 0.66,0.41 c -4.55,4.76 -10.46,7.93 -15.76,11.81 -8.52999,6.16 -16.81999,12.65 -24.83999,19.45 -42.19,36.24 -82.19,78.69 -111.24,126.29 15.58,8.13 29.13,19.6 42.84,30.47 111.87999,16.83 233.13999,11.38 344.41099,-7.48 -20.212,-25.39 -43.262,-44.2 -72.882,-57.73 -6.757,-3.08 -14.5,-4.06 -20.888,-7.45 l 0.502,-1.62 c -31.246,-10.13 -70.393,3.82 -98.451,17.18 -5.918,2.82 -18.142,11 -24.312,10.7 -15.46,6.74 -29.47,20.08 -45.06999,27.66 13.88999,-14.8 27.62999,-29.44 43.01999,-42.72 8.45,-7.31 20.226,-12.25 27.313,-20.79 11.114,-13.39 22.512,-42.95 31.313,-59.54 l 70.44,-134.16 c 8.183,-15.77 17.861,-31.17 25.295,-47.27 z"
id="path27" />
<path
fill="#fefefe"
d="m 964.27821,1929.74 c -10.33,-10.67 -21.54,-20.6 -32.39,-30.75 -24.19,-22.62 -49.54,-48.7 -80.57,-61.62 -48.9,-20.35 -126.66,8.19 -173.82,27.52 7.46,-1.79 14.92,-3.54 22.39,-5.24 26.36,-11.1 88.72,-18.77 116.64,-16.39 11,0.87 21.8,3.47 32,7.71 23.49,9.7 65.53,43.31 78.6,65.25 5.18,5.78 9.53,12.07 13.55,18.69 7.96,-1.22 15.78,-3.28 23.6,-5.17 z"
id="path28" />
<path
fill="#fefefe"
d="m 1011.8582,1670.44 c -15.57999,0.98 -76.79999,42.02 -91.81999,53.07 -8.83,6.5 -17.82,15.58 -27.2,20.88 -22.17,21.65 -44.55,39.98 -59.75,67.86 6.66,0.91 13.1,2.67 19.6,4.34 42.4,-59.17 96.6,-108.93 159.16999,-146.15 z"
id="path29" />
<path
fill="#fefefe"
d="m 776.49821,1041.5 -0.79,-0.02 c 3.25,5.84 31.72,19.8 39.4,24.26 35.45,20.61 69.44,45.78 101.42,71.45 40.5,32.51 82.9,73.97 108.95999,119.35 35.576,61.94 44.425,163.33 39.118,233.44 -2.405,29.61 -7.419,58.95 -14.978,87.67 25.748,-9.45 53.013,-15.97 77.32,-29.02 9.404,-22.28 15.153,-46.64 21.225,-70.02 20.908,-86.71 27.711,-180.32 5.568,-267.54 -11.1,-43.71 -28.055,-84.52 -46.893,-125.35 -12.229,7.13 -24.695,10.25 -38.55,12.63 -1.75,5.96 -4.095,11.92 -6.716,17.54 -14.194,30.45 -39.494,59.89 -71.64399,71.68 -20.94,-21.66 -58.11,-107.52 -65.92,-138.47 -49.37,-1.36 -98.33,-2.85 -147.52,-7.6 z"
id="path30" />
<path
fill="#000000"
d="m 1131.7722,1245.9 c -1.001,-0.07 -2.004,-0.16 -3.009,-0.16 -4.561,0.05 -9.125,1.95 -12.152,5.39 -2.654,3.02 -3.835,6.81 -3.533,10.8 0.419,5.57 3.449,9.13 7.531,12.59 2.072,0.41 4.329,0.74 6.44,0.73 3.227,-0.01 6.422,-1.83 8.534,-4.2 2.729,-3.05 3.784,-7.26 3.586,-11.28 -0.312,-6.34 -2.758,-9.81 -7.397,-13.87 z"
id="path31" />
<path
fill="#000000"
d="m 1137.8012,1374.87 c -0.9,0.05 -1.804,0.07 -2.698,0.19 -4.188,0.57 -8.239,3.48 -10.646,6.88 -2.563,3.68 -3.394,8.3 -2.277,12.65 1.244,5.01 4.495,7.42 8.688,10.01 1.207,0.12 1.249,0.17 2.632,0.08 3.693,-0.23 7.725,-2.38 10.036,-5.25 2.587,-3.21 3.275,-7.38 2.859,-11.4 -0.647,-6.26 -3.85,-9.43 -8.594,-13.16 z"
id="path32" />
<path
fill="#000000"
d="m 1125.5132,1487.88 c -1.47,0.01 -2.94,-0.01 -4.407,0.09 -4.327,0.3 -7.452,2.12 -10.165,5.51 -2.532,3.15 -3.69,6.68 -3.19,10.72 0.564,4.54 2.352,7.01 5.805,9.85 3.926,-0.01 7.127,-0.2 10.424,-2.55 3.31,-2.35 5.757,-5.87 6.303,-9.94 0.764,-5.69 -1.268,-9.41 -4.77,-13.68 z"
id="path33" />
<path
fill="#3d3d3d"
d="m 405.73821,501.369 c -37.66,-38.397 -98.5,-83.113 -153.62,-83.878 -29.26,-0.63 -57.54,10.525 -78.49,30.956 -26.61,26.322 -37.89,61.255 -37.8,98.142 0.06,22.269 3.87,44.259 13.58,64.408 3.34,14.654 30.02,37.79 42.81,45.825 43.72,27.484 108.53,32.949 158.01,21.112 7.06,-1.69 14.06,-3.791 21.01,-5.885 -0.6,-6.417 -1.31,-12.835 -1.53,-19.278 -1.33,-33.635 7.02,-72.173 18.37,-103.702 5.93,-15.544 13.35,-29.797 21.34,-44.346 -1.17,-2.161 -1.46,-2.227 -3.68,-3.354 z"
id="path34" />
<path
fill="#3d3d3d"
d="m 377.63821,541.423 c -9.33,-5.367 -18.02,-11.802 -27.32,-17.274 -27.29,-16.053 -61.51,-29.517 -93.47,-20.643 -14.05,3.898 -26.52,12.426 -33.53,25.474 -7.32,13.61 -8.18,30.088 -3.52,44.692 8.06,25.281 30.56,46.783 53.75,58.611 32.29,16.466 54.11,14.449 88.66,14.099 l 5.55,0.781 c 1.02,1.928 1.02,3.08 1.62,5.063 0.06,0.204 0.22,0.363 0.33,0.545 -1.33,-33.635 7.02,-72.173 18.37,-103.702 -3.42,-2.577 -6.38,-6.121 -10.44,-7.646 z"
id="path35" />
<path
fill="#fefefe"
d="m 405.73821,501.369 c -37.66,-38.397 -98.5,-83.113 -153.62,-83.878 -29.26,-0.63 -57.54,10.525 -78.49,30.956 -26.61,26.322 -37.89,61.255 -37.8,98.142 0.06,22.269 3.87,44.259 13.58,64.408 0.36,-4.011 -1.79,-8.874 -2.89,-12.807 -9.8,-34.775 -5.37,-79.659 12.58,-111.248 13.65,-24.023 33.93,-41.297 60.81,-48.455 49,-13.047 103.73,9.047 144.9,35.001 13.32,8.397 25.64,19.165 39.21,26.916 z"
id="path36" />
<path
fill="#000000"
d="m 377.63821,541.423 c -26.26,-27.845 -70.15,-50.615 -108.89,-51.5 -16.95,-0.388 -33.89,3.964 -46.47,15.918 -11.36,10.797 -16.89,25.89 -16.92,41.375 -0.06,24.367 13.34,45.714 30.25,62.291 26.37,25.866 61.95,40.171 98.89,39.759 9.21,-0.086 18.7,-0.911 27.71,-2.884 -34.55,0.35 -56.37,2.367 -88.66,-14.099 -23.19,-11.828 -45.69,-33.33 -53.75,-58.611 -4.66,-14.604 -3.8,-31.082 3.52,-44.692 7.01,-13.048 19.48,-21.576 33.53,-25.474 31.96,-8.874 66.18,4.59 93.47,20.643 9.3,5.472 17.99,11.907 27.32,17.274 z"
id="path37" />
<path
fill="#3d3d3d"
d="m 1504.8322,918.868 c -76.551,24.431 -160.727,26.793 -239.329,11.231 -23.764,-4.705 -46.634,-12.219 -70.162,-17.713 2.354,8.129 5.317,19.915 11.692,25.929 7.176,17.166 19.219,33.019 28.22,49.344 23.823,43.211 65.44,127.781 70.906,176.231 33.158,-4.98 65.772,-10.92 98.046,-20.15 66.831,-19.1 130.415,-50.76 191.262,-83.81 -7.883,0.06 -15.937,0.36 -23.789,-0.37 -22.622,-2.09 -39.999,-12.49 -54.365,-29.93 -22.289,-27.07 -23.538,-58.042 -20.304,-91.44 2.231,-6.671 4.843,-12.951 7.823,-19.322 z"
id="path38" />
<path
fill="#fefefe"
d="m 1504.8322,918.868 c -76.551,24.431 -160.727,26.793 -239.329,11.231 -23.764,-4.705 -46.634,-12.219 -70.162,-17.713 2.354,8.129 5.317,19.915 11.692,25.929 93.97,32.282 196.033,32.238 289.976,-0.125 2.231,-6.671 4.843,-12.951 7.823,-19.322 z"
id="path39" />
<path
fill="#f3d87a"
d="m 1652.1732,820.506 c -23.774,-22.069 -48.625,-42.719 -73.212,-63.854 -7.686,3.614 -14.28,8.742 -20.946,13.932 -17.804,14.905 -33.493,34.189 -39.579,57.037 10.108,17.144 59.471,53.046 77.706,68.679 l 135.564,115.54 c 20.31,17.32 40.972,34.08 61.692,50.91 11.674,9.48 23.054,19.83 35.479,28.28 17.563,-9.29 30.299,-22.04 39.413,-39.65 1.115,-2.36 2.577,-5.38 2.723,-8.02 0.337,-6.07 -4.489,-9.7 -8.409,-13.52 -30.052,-29.3 -62.312,-56.718 -94.228,-83.949 -7.308,5.379 -14.781,8.767 -23.225,11.962 -8.719,1.74 -15.703,3.469 -24.641,1.027 -6.692,-1.828 -12.529,-6.162 -15.721,-12.376 -7.322,-14.256 -1.457,-32.397 3.473,-46.42 -4.93,3.718 -9.835,7.544 -15.367,10.337 -11.145,5.627 -24.582,8.184 -36.601,3.787 -8.122,-2.971 -14.282,-9.093 -17.716,-16.991 -7.337,-16.874 -1.199,-39.185 5.181,-55.482 3.216,-7.01 6.172,-13.842 8.414,-21.229 z"
id="path40" />
<path
fill="#fefefe"
d="m 1768.3762,945.891 c -7.308,5.379 -14.781,8.767 -23.225,11.962 18.614,14.716 36.873,29.907 55.557,44.517 19.408,15.18 39.023,29.67 57.998,45.44 3.177,1.73 6.075,2.72 9.584,3.57 1.115,-2.36 2.577,-5.38 2.723,-8.02 0.337,-6.07 -4.489,-9.7 -8.409,-13.52 -30.052,-29.3 -62.312,-56.718 -94.228,-83.949 z"
id="path41" />
<path
fill="#ffe895"
d="m 1868.2902,1051.38 c 1.115,-2.36 2.577,-5.38 2.723,-8.02 0.337,-6.07 -4.489,-9.7 -8.409,-13.52 1.103,4.17 2.578,7.81 0.289,11.82 -0.629,1.1 -1.364,2.12 -2.089,3.16 -0.697,1 -1.387,2 -2.098,2.99 3.177,1.73 6.075,2.72 9.584,3.57 z"
id="path42" />
<path
fill="#fefefe"
d="m 1652.1732,820.506 c -23.774,-22.069 -48.625,-42.719 -73.212,-63.854 -7.686,3.614 -14.28,8.742 -20.946,13.932 17.513,15.739 36.589,29.973 54.742,44.971 10.188,8.417 20.069,18.846 31.002,26.18 3.216,-7.01 6.172,-13.842 8.414,-21.229 z"
id="path43" />
<path
fill="#3d3d3d"
d="m 1180.8762,925.6 c -11.55,11.881 -12.645,29.497 -19.599,43.765 16.293,36.115 35.457,71.785 35.912,112.325 -8.238,3.72 -13.655,3.57 -22.393,2.57 40.875,110.1 56.965,228.14 38.186,344.7 -3.31,20.54 -7.88,41.79 -13.354,61.87 -2.535,9.3 -7.29,19.08 -8.411,28.59 l 1.216,0.15 c 21.352,-13.92 41.958,-29.01 52.123,-53.24 7.946,-18.94 10.738,-41.21 14.28,-61.39 27.275,-155.46 -19.967,-335.5 -77.96,-479.34 z"
id="path44" />
<path
fill="#3d3d3d"
d="m 811.55821,242.924 c 1.48,2.023 2.79,2.59 5.05,3.594 1.45,-0.21 2.43,0.092 3.33,-1.231 1.86,-2.728 1.26,-6.446 0.99,-9.56 -3.47,-40.663 -17.08,-80.985 -49.39,-107.681 -22.89,-18.916 -55.28,-31.1105 -85.2,-28.1672 -11.38,1.1202 -39.53,7.6052 -46.84,16.4092 l 2.34,0.606 0.3,1.415 c -7.66,5.287 -16.04,8.907 -23.62,14.591 -9.4,7.055 -17.52,26.089 -19.63,37.588 -2.31,12.576 -0.39,23.68 3.23,35.775 25.28,-11.638 48.85,-20.023 76.8,-22.705 6.24,-0.599 13.41,-1.495 19.45,0.6 2.95,1.024 5.51,2.904 6.79,5.84 2.11,4.841 0.49,10.666 -1.65,15.198 -4.81,10.194 -13.53,19.026 -20.85,27.5 36.45,-7.529 74.85,-5.146 110.61,4.558 5.65,1.534 13.28,2.733 18.29,5.67 z"
id="path45" />
<path
fill="#fefefe"
d="m 811.55821,242.924 c 1.48,2.023 2.79,2.59 5.05,3.594 1.45,-0.21 2.43,0.092 3.33,-1.231 1.86,-2.728 1.26,-6.446 0.99,-9.56 -3.47,-40.663 -17.08,-80.985 -49.39,-107.681 -22.89,-18.916 -55.28,-31.1105 -85.2,-28.1672 -11.38,1.1202 -39.53,7.6052 -46.84,16.4092 l 2.34,0.606 c 22.75,-11.918 52.65,-10.397 76.47,-2.929 33.91,10.631 60.2,35.034 76.36,66.397 9.69,18.798 16.43,41.331 16.89,62.562 z"
id="path46" />
<path
fill="#f3d87a"
d="m 1535.7012,625.842 c -6.27,2.36 -12.483,4.866 -18.635,7.517 -53.624,23.685 -100.025,69.223 -120.948,124.403 -5.23,13.793 -11.509,36.884 -4.411,50.965 3.853,7.642 35.636,16.069 45.062,19.222 8.977,3.05 18.046,5.664 27.161,8.257 -2.347,-32.224 13.317,-64.448 32.738,-89.388 19.651,-25.236 44.783,-47.206 77.354,-51.38 -11.454,-23.743 -25.249,-46.712 -38.321,-69.596 z"
id="path47" />
<path
fill="#3d3d3d"
d="m 648.62821,1025.58 -0.14,0.73 c 9.38,6.55 21.01,11.36 31.06,16.98 8.77,4.89 16.85,11.14 24.7,17.38 7.89,6.27 15.7,12.75 23.07,19.64 7.62,7.14 15.7,18.49 25,23.13 8.82,4.4 19.7,6.23 29.06,9.47 29.9,10.36 69.43,24.31 95.74,41.57 -11.56,17.62 -22.07,36.66 -30.1,56.17 62.59,14.2 118.96,34.51 169.26999,75.55 5.9,4.55 11.43,9.38 16.95,14.38 -13.56,-29.4 -33.82999,-56.36 -53.97999,-81.51 -36.92,-46.08 -177.01,-159.62 -231.61,-177.87 -11.49,-3.85 -25.1,-4.79 -37.13,-6.64 -20.59,-3.17 -41.18,-6.71 -61.89,-8.98 z"
id="path48" />
<path
fill="#3d3d3d"
d="m 258.98821,1422.86 c -4.94,0.28 -9.77,0.83 -14.52,2.32 -2.36,0.74 -4.92,1.99 -5.83,4.47 -1.28,3.48 -0.24,6.97 1.54,10.03 14.14,24.19 60.52,44.37 86.88,49.71 15.69,3.17 32.17,3.45 47.64,7.46 l 1.34,0.36 c -18.11,4.42 -38.97,3.98 -57.52,2.72 -11.04,-0.74 -22.83,-2.47 -33.79,-0.56 -4.06,0.71 -7.72,2.14 -9.95,5.8 -1.63,2.67 -2.02,5.86 -0.97,8.83 1.71,4.83 8.8,9.9 13.23,12.07 35.18,17.23 87.09,8.39 121.97,-4.85 -5.81,27.24 -19.11,30.19 -32.72,51.04 -5.57,8.54 -8.22,18.79 -5.62,28.84 1.49,5.73 7.19,16.52 12.99,18.81 2.71,1.07 4.06,1.01 6.73,-0.1 6.77,-2.81 10.63,-14.47 13.56,-20.76 22.97,-30.59 55.31,-33.72 70.56,-72.16 -45.16,-12.02 -78.24,-45.32 -102.89,-83.6 -33.15,-6.97 -65.66,-17.58 -99.87,-17.35 17.91,9.28 36.54,18.29 55.34,25.68 12.52,4.92 25.47,8.72 37.85,14.02 -28.03,-2.08 -73.94,-12.57 -96.61,-29.15 -5.7,-4.16 -8.25,-6.58 -9.34,-13.63 z"
id="path49" />
<path
fill="#3d3d3d"
d="m 1545.8252,874.668 c -1.233,0 -2.163,0.16 -3.293,0.678 -3.748,1.718 -6.549,3.796 -7.79,7.932 -6.078,20.239 41.657,64.988 54.979,80.132 8.55,9.719 17.488,20.282 23.86,31.596 1.799,3.193 3.085,6.224 1.872,9.914 -0.557,1.69 -1.103,1.61 -2.525,2.43 -3.461,-1.86 -10.912,-16.516 -15.131,-21.385 -10.223,-11.796 -22.365,-22.138 -33.222,-33.376 -11.982,-12.402 -22.463,-25.771 -33.012,-39.378 -8.522,11.689 -13.22,25.873 -15.407,40.064 -3.638,23.616 5.029,45.303 18.775,64.165 5.747,6.61 12.131,11.73 20.013,15.54 8.258,3.96 17.192,6.31 26.328,6.92 31.351,2.36 96.757,-9.73 127.161,-20.29 -11.515,-9.22 -65.437,-56.81 -73.805,-60.22 -2.149,3.19 -0.525,8.657 -2.093,12.578 -4.347,-0.701 -11.927,-16.416 -14.463,-20.454 -19.077,-30.378 -43.671,-55.244 -72.247,-76.846 z"
id="path50" />
<path
fill="#fefefe"
d="m 1115.8892,1071.05 c -1.636,-0.05 -0.849,-0.29 -2.272,0.95 0.359,8.01 6.373,16.47 9.916,23.55 10.665,21.31 18.827,45.01 26.319,67.61 35.484,107.03 34.798,217.31 6.118,326.06 -4.881,18.51 -11.368,36.64 -17.333,54.82 6.819,-2.89 13.579,-5.92 20.277,-9.08 l 14.264,-7.53 c 21.115,-67.48 32.081,-127.84 34.433,-198.6 1.07,-32.2 0.394,-65.1 -5.02,-96.92 -8.717,-51.22 -25.881,-101.74 -43.434,-150.5 -14.687,-2.71 -29.053,-5.66 -43.268,-10.36 z"
id="path51" />
<path
fill="#f3d87a"
d="m 1486.6172,583.051 c -29.148,-16.664 -62.795,-21.301 -95.346,-12.419 -27.074,7.387 -53.817,26.591 -68.114,50.981 -9.704,12.838 -15.524,30.91 -17.834,46.713 -5.463,37.38 4.449,68.232 26.611,97.987 5.512,-14.768 8.805,-29.951 15.127,-44.527 26.153,-60.289 73.627,-101.483 129.25,-133.75 z"
id="path52" />
<path
fill="#fefefe"
d="m 1486.6172,583.051 c -29.148,-16.664 -62.795,-21.301 -95.346,-12.419 -27.074,7.387 -53.817,26.591 -68.114,50.981 l 0.784,0.525 c 3.771,-0.762 9.599,-7.923 12.929,-10.656 9.431,-7.741 20.137,-13.952 31.092,-19.267 36.249,-17.587 70.59,-17.122 108.349,-4.179 z"
id="path53" />
<path
fill="#c76362"
d="m 939.42821,1021.37 c -3.25,-0.63 -6.97,-0.59 -10.29,-0.87 12.83,54.13 39.03,105.47 63.07,155.29 24.59999,-15.58 47.16999,-41.44 60.27899,-67.36 3.908,-7.73 10.876,-22.29 8.033,-30.84 -3.748,0.5 -9.707,7.52 -12.882,10.23 -12.23,10.36 -25.36,19.61 -39.24,27.63 -5.65,3.04 -11.41999,5.46 -17.35999,7.86 12.83999,-14.92 28.28999,-27.45 44.35999,-38.7 5.16,-3.61 18.947,-10.95 21.616,-16.52 0.641,-1.33 -0.01,-2.21 -0.408,-3.52 -5.788,-0.99 -13.158,1.14 -19.078,1.88 -22.49,2.85 -45.10999,7.64 -67.81999,7.47 24.54,-18.41 55.48999,-14.7 84.19799,-17.79 -1.47,-3.93 -3.032,-9.16 -5.648,-12.42 -2.04,-2.54 -5.03,-4.44 -8.05,-5.56 -20.26,-7.51 -76.87999,-13.72 -100.77999,-16.78 z"
id="path54" />
<path
fill="#f3d87a"
d="m 1554.8792,596.826 c -1.791,-2.733 -4.184,-4.951 -7.097,-6.441 -12.676,-6.482 -29.096,-3.464 -41.994,0.869 -56.566,16.384 -108.031,66.652 -135.917,117.278 -16.785,30.471 -28.54,70.081 -18.381,104.686 1.708,5.817 4.678,11.802 10.402,14.554 5.217,2.508 11.366,1.608 16.538,-0.498 2.009,-0.818 3.939,-1.769 5.85,-2.792 -11.271,-17.392 -11.151,-34.734 -6.962,-54.457 10.956,-51.574 54.34,-102.947 98.124,-131.161 14.647,-9.438 48.361,-29.85 65.716,-25.595 4.424,1.084 7.601,4.772 10.751,7.847 1.822,-8.148 3.582,-15.882 2.97,-24.29 z"
id="path55" />
<path
fill="#fefefe"
d="m 1554.8792,596.826 c -1.791,-2.733 -4.184,-4.951 -7.097,-6.441 -12.676,-6.482 -29.096,-3.464 -41.994,0.869 8.145,1.919 17.173,0.689 25.584,1.623 7.873,0.875 15.693,3.303 23.507,3.949 z"
id="path56" />
<path
fill="#fefefe"
d="m 1194.6122,931.743 c -1.182,4.617 19.475,64.664 22.382,74.267 23.819,78.65 43.264,158.94 51.658,240.82 7.665,74.78 3.088,145.11 -15.923,217.87 3.389,-4.09 6.356,-8.35 9.333,-12.74 17.47,-28.61 21.882,-66.28 25.391,-99.14 13.522,-126.62 -14.131,-253.95 -67.517,-368.649 -6.073,-13.048 -17.643,-42.925 -25.324,-52.428 z"
id="path57" />
<path
fill="#fefefe"
d="m 1221.4222,1510.7 c -7.602,4.86 -16.209,9.77 -21.543,17.23 -4.948,6.92 -8.464,15.57 -12.342,23.14 -5.436,10.41 -10.998,20.75 -16.686,31.02 -21.541,39.02 -42.576,78.32 -63.1,117.89 -11.489,22.14 -24.193,44.64 -32.818,68.07 15.569,-9.01 31.481,-16.73 48.448,-22.74 7.577,-24.17 18.143,-48.01 27.478,-71.56 10.147,-25.6 19.53,-51.61 30.966,-76.67 13.103,-28.71 28.312,-56.89 39.597,-86.38 z"
id="path58" />
<path
fill="#f3d87a"
d="m 1577.5572,705.99 c -14.978,0.474 -26.35,7.044 -38.144,15.689 -30.311,22.218 -56.847,58.79 -62.514,96.566 -1.054,7.03 -1.447,14.936 3.254,20.819 3.826,4.788 9.557,4.811 15.154,5.311 l 16.929,-0.42 c -1.215,-1.191 -2.436,-2.393 -3.532,-3.697 -3.961,-4.715 -4.402,-10.838 -3.77,-16.747 2.811,-26.286 27.068,-54.913 47.091,-70.525 9.426,-7.35 19.389,-13.746 31.794,-12.243 1.137,-4.163 2.41,-8.533 2.758,-12.847 0.736,-9.135 -3.245,-15.3 -9.02,-21.906 z"
id="path59" />
<path
fill="url(#Gradient1)"
d="m 1187.8022,1073.83 c -6.01,-49.83 -32.202,-89.792 -51.585,-134.825 -1.929,4.307 -4.145,8.34 -6.487,12.427 l 0.411,0.893 c 5.927,12.652 12.166,23.851 17.081,37.083 0.646,1.243 1.032,2.241 1.445,3.562 -15.703,14.89 -38.249,31.55 -49.901,49.38 19.718,-6.81 45.65,-16.2 66.649,-10.17 l 0.102,1.37 c -14.201,9.14 -42.238,11.42 -59.466,14.51 l 4.544,9.3 c 25.596,6.12 51.727,10 77.207,16.47 z"
id="path60"
style="fill:url(#Gradient1)" />
<path
fill="#3d3d3d"
d="m 1755.2442,938.188 c 13.348,-12.531 28.344,-25.97 28.899,-45.799 0.424,-15.128 -7.149,-28.835 -17.523,-39.318 -4.73,-4.779 -14.737,-12.477 -21.645,-12.453 -1.215,0.005 -2.392,0.303 -3.149,1.285 -4.436,5.756 -5.439,22.19 -7.57,29.764 -4.59,16.318 -10.781,32.046 -16.575,47.955 5.296,-7.431 9.018,-15.709 13.817,-23.426 0.053,0.109 0.096,0.225 0.161,0.329 2.37,3.758 -13.555,32.358 -13.037,41.899 0.169,3.111 0.947,4.778 3.282,6.774 4.523,0.895 8.968,-1.934 12.814,-4.018 17.116,-9.277 31.182,-36.467 28.988,-55.896 -0.736,-6.513 -6.91,-16.985 -5.441,-21.932 8.31,9.884 13.246,20.474 12.303,33.647 -1.072,14.96 -8.391,28.243 -15.324,41.189 z"
id="path61" />
<path
fill="#fefefe"
d="m 1755.2442,938.188 c 13.348,-12.531 28.344,-25.97 28.899,-45.799 0.424,-15.128 -7.149,-28.835 -17.523,-39.318 -4.73,-4.779 -14.737,-12.477 -21.645,-12.453 -1.215,0.005 -2.392,0.303 -3.149,1.285 -4.436,5.756 -5.439,22.19 -7.57,29.764 -4.59,16.318 -10.781,32.046 -16.575,47.955 5.296,-7.431 9.018,-15.709 13.817,-23.426 3.515,-8.055 15.259,-32.161 21.854,-36.928 2.497,0.755 3.224,2.184 4.913,4.084 8.31,9.884 13.246,20.474 12.303,33.647 -1.072,14.96 -8.391,28.243 -15.324,41.189 z"
id="path62" />
<path
fill="#fefefe"
d="m 1281.9402,1841.15 c -29.813,-34.01 -80.55,-79.31 -127.271,-82.13 -37.997,-2.29 -82.675,28.07 -113.551,48.37 6.17,0.3 18.394,-7.88 24.312,-10.7 28.058,-13.36 67.205,-27.31 98.451,-17.18 38.055,7.52 73.95,35.02 96.54,65.89 z"
id="path63" />
<path
fill="#3d3d3d"
d="m 1693.3372,808.588 c -5.945,4.262 -39.933,73.328 -40.666,81.431 -0.207,2.278 1.042,4.92 2.648,6.542 4.085,4.126 10.22,4.237 15.653,4.711 9.531,-1.269 19.169,-3.617 27.133,-9.249 8.067,-5.706 13.359,-13.818 14.648,-23.669 2.767,-21.152 -7.225,-43.145 -19.416,-59.766 z"
id="path64" />
<path
fill="#fefefe"
d="m 1695.5442,790.412 c -7.73,0.032 -14.462,0.838 -21.806,3.188 -2.031,13.51 -8.515,27.361 -13.261,40.143 -5.194,13.99 -9.374,27.709 -12.258,42.373 10.467,-20.926 24.482,-39.803 35.362,-60.511 2.182,-3.912 4.64,-7.694 6.988,-11.51 3.32,1.623 6.413,3.315 9.242,5.723 16.947,14.42 18.364,37.668 20.297,58.186 3.612,-9.734 7.15,-19.427 8.552,-29.766 1.642,-12.105 0.345,-25.741 -7.5,-35.589 -6.667,-8.371 -15.397,-11.258 -25.616,-12.237 z"
id="path65" />
<path
fill="#c76362"
d="m 1072.7152,1022.19 c -1.293,0.52 -3.158,1.07 -4.244,1.92 -2.615,2.04 -2.353,4.78 -2.82,7.81 l -0.847,-0.18 c -0.955,-0.94 -2.065,-2.15 -3.221,-2.87 -3.473,-2.19 -9.673,-0.5 -13.585,0.06 1.37,1.55 2.689,3.14 3.955,4.77 10.456,13.62 21.205,35.93 18.818,53.3 2.307,-0.45 4.606,-0.93 6.897,-1.45 8.196,-1.86 21.774,-5.04 26.165,-12.89 2.067,-3.7 1.506,-8.22 0.353,-12.1 -5.128,-17.29 -15.749,-29.82 -31.471,-38.37 z"
id="path66" />
<path
fill="#c76362"
d="m 1129.7302,951.432 c -13.6,22.402 -26.726,46.45 -44.307,65.968 4.261,4.51 8.651,8.7 13.287,12.82 9.584,-14.23 28.886,-37.069 46.582,-40.479 0.641,-0.124 1.286,-0.222 1.93,-0.333 -4.915,-13.232 -11.154,-24.431 -17.081,-37.083 z"
id="path67" />
<path
fill="#3d3d3d"
d="m 1166.5852,876.649 c -4.878,4.603 -19.364,45.536 -21.141,54.122 2.801,6.395 6.332,23.952 11.783,27.46 l 1.796,-2.678 c 5.997,-18.841 16.982,-32.324 26.06,-49.354 -4.182,-10.894 -11.326,-20.473 -18.498,-29.55 z"
id="path68" />
<path
fill="#f3d87a"
d="m 1872.8762,1075.78 c -5.548,2.63 -11.427,12.46 -15.142,17.34 6.027,5.75 12.688,10.86 18.873,16.44 9.249,8.35 18.002,17.24 27.219,25.64 4.288,-2.42 8.25,-6.01 11.247,-9.92 2.395,-3.12 3.669,-6.02 3.051,-9.99 -5.618,-7.2 -36.999,-33.59 -45.248,-39.51 z"
id="path69" />
</svg>

After

Width:  |  Height:  |  Size: 40 KiB

View File

@ -29,10 +29,18 @@ templ AppPage(funcs ...AppPageOptionFunc) {
<section class="section">
<div class="level is-mobile">
<div class="level-left">
<h1 class="title is-size-1">Kouiz</h1>
<div class="level-item">
<figure class="image mr-3" style="width:100px">
<img class="is-rounded" src={ string(BaseURL(ctx, WithPath("/assets/panda.svg"))) }/>
</figure>
<a href={ BaseURL(ctx) }>
<h1 class="title is-size-1">Kouiz</h1>
</a>
</div>
</div>
<div class="level-right">
<div class="buttons is-right level-item">
<a class="button is-medium" href={ BaseURL(ctx, WithPath("/quiz/leaderboard")) }><span class="icon"><i class="fa fa-trophy"></i></span></a>
<a class="button is-medium" href={ BaseURL(ctx, WithPath("/auth/logout")) }><span class="icon"><i class="fa fa-sign-out-alt"></i></span></a>
</div>
</div>

View File

@ -64,16 +64,47 @@ func AppPage(funcs ...AppPageOptionFunc) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"container\"><section class=\"section\"><div class=\"level is-mobile\"><div class=\"level-left\"><h1 class=\"title is-size-1\">Kouiz</h1></div><div class=\"level-right\"><div class=\"buttons is-right level-item\"><a class=\"button is-medium\" href=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"container\"><section class=\"section\"><div class=\"level is-mobile\"><div class=\"level-left\"><div class=\"level-item\"><figure class=\"image mr-3\" style=\"width:100px\"><img class=\"is-rounded\" src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 templ.SafeURL = BaseURL(ctx, WithPath("/auth/logout"))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var3)))
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(string(BaseURL(ctx, WithPath("/assets/panda.svg"))))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/common/component/app_page.templ`, Line: 34, Col: 89}
}
_, 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, "\"><span class=\"icon\"><i class=\"fa fa-sign-out-alt\"></i></span></a></div></div></div>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\"></figure><a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 templ.SafeURL = BaseURL(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var4)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\"><h1 class=\"title is-size-1\">Kouiz</h1></a></div></div><div class=\"level-right\"><div class=\"buttons is-right level-item\"><a class=\"button is-medium\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 templ.SafeURL = BaseURL(ctx, WithPath("/quiz/leaderboard"))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var5)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\"><span class=\"icon\"><i class=\"fa fa-trophy\"></i></span></a> <a class=\"button is-medium\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 templ.SafeURL = BaseURL(ctx, WithPath("/auth/logout"))
_, 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, 5, "\"><span class=\"icon\"><i class=\"fa fa-sign-out-alt\"></i></span></a></div></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -81,7 +112,7 @@ func AppPage(funcs ...AppPageOptionFunc) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</section></div>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</section></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}

View File

@ -38,7 +38,7 @@ func NewPageOptions(funcs ...PageOptionFunc) *PageOptions {
templ Page(funcs ...PageOptionFunc) {
{{ opts := NewPageOptions(funcs...) }}
<!DOCTYPE html>
<html data-theme="dark">
<html data-theme="light">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>

View File

@ -65,7 +65,7 @@ func Page(funcs ...PageOptionFunc) templ.Component {
}
ctx = templ.ClearChildren(ctx)
opts := NewPageOptions(funcs...)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html data-theme=\"dark\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><title>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html data-theme=\"light\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><title>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}

View File

@ -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>
}
}

View File

@ -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

View File

@ -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)
}
}

View File

@ -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 {

View 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>
}
}

View 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

View File

@ -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
}

View 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
}

View File

@ -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) {

View 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
}

View File

@ -35,7 +35,7 @@ func NewQuizHandlerFromConfig(ctx context.Context, conf *config.Config) (*quiz.H
return nil, errors.WithStack(err)
}
return quiz.NewHandler(store, conf.Quiz.PlayInterval), nil
return quiz.NewHandler(store, conf.Quiz.PlayInterval, conf.Quiz.PlayPeriod), nil
}
func loadEmbeddedQuizz(ctx context.Context, st *store.Store) error {

View File

@ -19,12 +19,17 @@ type Player struct {
Name string
UserID string `gorm:"index"`
UserEmail string `gorm:"index"`
UserProvider string `gorm:"index"`
Score int
PlayedAt time.Time
SelectedEntry *uint
SelectedTurn *uint
SelectedAt time.Time
SelectedAnswer *int
}
type QuizTurn struct {

View File

@ -7,16 +7,29 @@ import (
"gorm.io/gorm"
)
func (s *Store) UpsertPlayer(ctx context.Context, player *Player) error {
return errors.WithStack(s.Do(ctx, func(db *gorm.DB) error {
func (s *Store) UpsertPlayer(ctx context.Context, name string, userEmail string, userProvider string) (*Player, error) {
var player *Player
err := s.Do(ctx, func(db *gorm.DB) error {
var existing *Player
err := db.Find(&existing, "user_id = ? and user_provider = ?", player.UserID, player.UserProvider).Error
err := db.First(&existing, "user_email = ? and user_provider = ?", userEmail, userProvider).Error
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return errors.WithStack(err)
}
if existing != nil {
player.Model = existing.Model
if existing != nil && existing.ID != 0 {
err := db.Model(&existing).UpdateColumn("name", name).Error
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return errors.WithStack(err)
}
player = existing
return nil
}
player = &Player{
Name: name,
UserEmail: userEmail,
UserProvider: userProvider,
}
if err := db.Save(player).Error; err != nil {
@ -24,5 +37,26 @@ func (s *Store) UpsertPlayer(ctx context.Context, player *Player) error {
}
return nil
}))
})
if err != nil {
return nil, errors.WithStack(err)
}
return player, nil
}
func (s *Store) GetPlayerRank(ctx context.Context, playerID uint) (int, error) {
var rank int
err := s.Tx(ctx, func(db *gorm.DB) error {
err := db.Model(&Player{}).Select("rank() over (order by score desc) player_rank").First(&rank).Error
if err != nil {
return errors.WithStack(err)
}
return nil
})
if err != nil {
return 0, errors.WithStack(err)
}
return rank, nil
}

View File

@ -3,8 +3,10 @@ package store
import (
"context"
"math/rand/v2"
"slices"
"time"
"forge.cadoles.com/wpetit/kouiz/internal/timex"
"github.com/pkg/errors"
"gorm.io/gorm"
)
@ -49,7 +51,7 @@ func (s *Store) UpsertQuizEntry(ctx context.Context, entry *QuizEntry) error {
}))
}
func (s *Store) GetQuizTurn(ctx context.Context, playInterval time.Duration) (*QuizTurn, error) {
func (s *Store) GetQuizTurn(ctx context.Context, playInterval time.Duration, period timex.PeriodType) (*QuizTurn, error) {
var quizTurn *QuizTurn
err := s.Tx(ctx, func(tx *gorm.DB) error {
now := time.Now().UTC()
@ -63,9 +65,13 @@ func (s *Store) GetQuizTurn(ctx context.Context, playInterval time.Duration) (*Q
return nil
}
interval := timex.NewInterval(playInterval, period)
current := interval.GetCurrent()
start, end := interval.GetIntervalBounds(current, time.Now().UTC())
quizTurn = &QuizTurn{
StartedAt: now.Round(time.Hour),
EndedAt: now.Add(playInterval).Round(time.Hour),
StartedAt: start.UTC(),
EndedAt: end.UTC(),
}
alreadyUsed := make([]uint, 0)
@ -93,6 +99,7 @@ func (s *Store) GetQuizTurn(ctx context.Context, playInterval time.Duration) (*Q
ID: entryIDs[index],
},
})
entryIDs = slices.Delete(entryIDs, index, index+1)
}
if err := tx.Save(quizTurn).Error; err != nil {

127
internal/timex/interval.go Normal file
View File

@ -0,0 +1,127 @@
package timex
import (
"fmt"
"time"
)
type Interval struct {
intervalDuration time.Duration
periodType PeriodType
}
type PeriodType string
const (
PeriodHourly PeriodType = "hourly"
PeriodDaily PeriodType = "daily"
PeriodMonthly PeriodType = "monthly"
PeriodYearly PeriodType = "yearly"
)
func NewInterval(intervalDuration time.Duration, periodType PeriodType) *Interval {
return &Interval{
intervalDuration: intervalDuration,
periodType: periodType,
}
}
func (i *Interval) GetCurrent() int {
return i.GetIntervalForTime(time.Now().UTC())
}
func (i *Interval) GetIntervalForTime(t time.Time) int {
t = t.UTC()
periodStart := i.getPeriodStart(t)
elapsed := t.Sub(periodStart)
intervalIndex := int(elapsed / i.intervalDuration)
// S'assurer que l'index n'est pas négatif
if intervalIndex < 0 {
intervalIndex = 0
}
return intervalIndex
}
func (i *Interval) getPeriodStart(t time.Time) time.Time {
t = t.UTC()
switch i.periodType {
case PeriodHourly:
return time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), 0, 0, 0, time.UTC)
case PeriodDaily:
return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, time.UTC)
case PeriodMonthly:
return time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, time.UTC)
case PeriodYearly:
return time.Date(t.Year(), 1, 1, 0, 0, 0, 0, time.UTC)
default:
return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, time.UTC)
}
}
func (i *Interval) GetTotalIntervalsInPeriod(referenceTime time.Time) int {
referenceTime = referenceTime.UTC()
periodStart := i.getPeriodStart(referenceTime)
periodEnd := i.getPeriodEnd(referenceTime)
totalDuration := periodEnd.Sub(periodStart)
return int(totalDuration / i.intervalDuration)
}
func (i *Interval) getPeriodEnd(t time.Time) time.Time {
t = t.UTC()
switch i.periodType {
case PeriodHourly:
return time.Date(t.Year(), t.Month(), t.Day(), t.Hour()+1, 0, 0, 0, time.UTC)
case PeriodDaily:
return time.Date(t.Year(), t.Month(), t.Day()+1, 0, 0, 0, 0, time.UTC)
case PeriodMonthly:
return time.Date(t.Year(), t.Month()+1, 1, 0, 0, 0, 0, time.UTC)
case PeriodYearly:
return time.Date(t.Year()+1, 1, 1, 0, 0, 0, 0, time.UTC)
default:
return time.Date(t.Year(), t.Month(), t.Day()+1, 0, 0, 0, 0, time.UTC)
}
}
func (i *Interval) GetIntervalBounds(index int, referenceTime time.Time) (time.Time, time.Time) {
referenceTime = referenceTime.UTC()
periodStart := i.getPeriodStart(referenceTime)
intervalStart := periodStart.Add(time.Duration(index) * i.intervalDuration)
intervalEnd := intervalStart.Add(i.intervalDuration)
return intervalStart.UTC(), intervalEnd.UTC()
}
func (i *Interval) GetIntervalInfo(t time.Time) IntervalInfo {
t = t.UTC()
index := i.GetIntervalForTime(t)
start, end := i.GetIntervalBounds(index, t)
return IntervalInfo{
Index: index,
Start: start,
End: end,
Duration: i.intervalDuration,
Reference: t,
}
}
type IntervalInfo struct {
Index int
Start time.Time
End time.Time
Duration time.Duration
Reference time.Time
}
func (ii IntervalInfo) String() string {
return fmt.Sprintf("Interval %d: %s -> %s (Duration: %v)",
ii.Index,
ii.Start.Format("2006-01-02 15:04:05"),
ii.End.Format("2006-01-02 15:04:05"),
ii.Duration)
}