Files
Forge-Engine/game_engine/rendering/quiz_box_config.py
T
williamandClaude Sonnet 5 b2e933f322
Build and deploy / test-python (push) Successful in 11m12s
Build and deploy / test-js (push) Successful in 53s
Build and deploy / lint-python (push) Successful in 3m56s
Build and deploy / lint-js (push) Successful in 3m1s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Build and deploy / sonarqube (push) Failing after 3m58s
Reorganisation game/document : renommage screens->game_engine + sous-dossiers game/ dans routes, scripts, static, templates, tests
Prepare la scission a venir entre l'editeur Jeu 2D et le futur editeur
Support de formation (voir docs/plan/PLAN.md), sans toucher a
l'architecture en couches existante :

- screens/ renomme en game_engine/ (nom clair pour le moteur du jeu 2D,
  avant l'arrivee d'un second "moteur" cote document) : ~85 imports
  corriges, contrat import-linter mis a jour, meme forme de couches.
- routes/, scripts/, static/, templates/, tests/ : tout ce qui est
  propre au jeu 2D deplace dans un sous-dossier game/ de chacun
  (routes/game/, static/game/, templates/game/, tests/game/,
  scripts/game/) ; ce qui est partage par le site (auth, onboarding,
  dashboard, uploads, db/) reste a la racine de chaque dossier. Un
  sous-dossier document/ (vide) cree dans chacun pour le futur chantier.
- styles/ volontairement inchange : les 3 fichiers sources sont
  concatenes en un seul static/style.css charge par tout le site,
  scinder leur CONTENU (editeur vs partage) serait un refactor CSS
  distinct, pas un deplacement mecanique.
- Chaine d'export SCORM (publish/build_scorm_package.py) mise a jour en
  profondeur : copie des assets, URLs d'icones relatives a
  static/style.css (qui ne bouge pas), manifeste, wrapper SCORM.
- Deux regressions d'un sweep de renommage anterieur corrigees au passage
  (screens.js/screens/scene-objects incorrectement convertis en
  game_engine.js/game_engine/scene-objects dans des commentaires).
- Effet de bord Windows decouvert et corrige : git mv + Path.write_text
  convertissent des fichiers en CRLF (core.autocrlf=true) - ~189 fichiers
  normalises en LF.
- .eslintrc.json/package.json : uniquement les chemins de glob mis a jour
  (static/game/js/...) ; la preparation eslint-plugin-unicorn du lot 7
  reste volontairement non committee (package-lock.json restaure a la
  version precedente).

Verifications : ruff, mypy --strict (391 fichiers), vulture, bandit,
lint-imports tous verts ; 591/591 tests Python, 276/276 tests JS ;
demarrage serveur + requetes HTTP manuelles confirmant que les assets
deplaces repondent en 200 au nouvel emplacement et 404 a l'ancien.

SKIP=djlint : backlog H021 (styles inline) deja documente comme dette
assumee dans CODE_QUALITY.md section 6, aucun template touche par ce
commit au-dela d'un deplacement de fichier.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 12:27:53 +02:00

102 lines
4.7 KiB
Python

import json
from typing import Any
# Réglages "❓ Boîte à quiz" propres au quiz AUTONOME/plein écran (demande
# explicite : "étendre les fonctionnalités de la boîte à quiz pour les RH
# qui veulent juste faire un quiz") — DISTINCT de "🎨 Style"
# (dialogue_box_style.py, partagé avec dialogue_box/score_widget) : ce
# fichier ne gère QUE plein écran/minuteur/modèle, jamais police/couleur.
#
# DEUX catégories de modèles bien distinctes (demande explicite, précisée
# après coup — les 5 premiers modèles construits avant cette précision
# étaient tous de la première catégorie) :
# - "dialog_template" (modèles "boîte de dialogue", visibles SEULEMENT
# quand fullscreen=False) : variantes de forme/couleur/interaction sur
# une structure HTML qui reste globalement la même que "Classique".
# - "page_template" (modèles "page de quiz", visibles SEULEMENT quand
# fullscreen=True) : thème complet et autonome — police, formes,
# couleurs ET structure HTML totalement propres à CHAQUE modèle,
# aucune structure partagée entre eux ni avec les modèles "boîte".
# Les deux réglages sont conservés INDÉPENDAMMENT (jamais écrasés l'un par
# l'autre) : activer/désactiver le plein écran change juste lequel des
# deux s'applique, sans jamais perdre le choix de l'autre.
DEFAULT_QUIZ_BOX_CONFIG: dict[str, Any] = {
"fullscreen": False,
"timer_mode": "aucun",
"timer_seconds": 20,
"dialog_template": "defaut",
"page_template": "classique",
}
# "aucun" : pas de minuteur (réglage par défaut, jamais imposé — demande
# explicite du créateur). "question" : redémarre à chaque question.
# "quiz" : un seul compte à rebours pour l'ensemble du quiz.
TIMER_MODES = ["aucun", "question", "quiz"]
# "defaut" (Classique) = le CSS/HTML déjà existant, AUCUN changement
# visuel pour un quiz non reconfiguré (voir quiz_box_templates.py) —
# "Verre dépoli"/"Néon"/"Minimal"/"Ludique" ont été retirés (demande
# explicite : "supprime tous les modele de boite sauf le base").
# "manga_dialogue" (Manga) : pendant, en petite carte flottante (jamais
# plein écran), du modèle "page de quiz" "Manga" — validé par le créateur
# via une maquette interactive avant intégration (voir
# game_engine/rendering/quiz_box_templates.py::render_quiz_box_manga_dialogue).
# Clé DIFFÉRENTE du modèle "page" "manga" (registre d'effets JS partagé
# entre les deux catégories, voir static/game/js/play/quiz-box-templates.js)
# — reste proche de la structure de "Classique" (demande explicite :
# les modèles "boîte" ne changent que forme/couleur/interaction).
QUIZ_BOX_DIALOG_TEMPLATES = ["defaut", "manga_dialogue"]
QUIZ_BOX_DIALOG_TEMPLATE_LABELS = {
"defaut": "Classique",
"manga_dialogue": "Manga",
}
# "classique" (Classique) : modèle de PAGE DE QUIZ de base — sobre/
# professionnel, reprend les couleurs par défaut du modèle "boîte de
# dialogue" de base (demande explicite : "respecte les couleurs du modele
# de boite de base pour le modele de page de base") plutôt que sa propre
# palette, contrairement à "manga" qui est un thème pleinement autonome.
# "manga" : thème/typographie/structure entièrement dédiés, validé par le
# créateur via une maquette interactive avant intégration (voir
# game_engine/rendering/quiz_box_templates.py::render_quiz_box_page_manga).
QUIZ_BOX_PAGE_TEMPLATES = ["classique", "manga"]
QUIZ_BOX_PAGE_TEMPLATE_LABELS = {
"classique": "Classique",
"manga": "Manga",
}
_MIN_TIMER_SECONDS = 3
_MAX_TIMER_SECONDS = 600
def sanitize_quiz_box_config(raw_config: Any) -> dict[str, Any]:
config = dict(DEFAULT_QUIZ_BOX_CONFIG)
if not isinstance(raw_config, dict):
return config
config["fullscreen"] = bool(raw_config.get("fullscreen"))
if raw_config.get("timer_mode") in TIMER_MODES:
config["timer_mode"] = raw_config["timer_mode"]
raw_timer_seconds = raw_config.get("timer_seconds")
try:
if raw_timer_seconds is None:
raise ValueError
config["timer_seconds"] = max(_MIN_TIMER_SECONDS, min(_MAX_TIMER_SECONDS, int(raw_timer_seconds)))
except (TypeError, ValueError):
pass
if raw_config.get("dialog_template") in QUIZ_BOX_DIALOG_TEMPLATES:
config["dialog_template"] = raw_config["dialog_template"]
if raw_config.get("page_template") in QUIZ_BOX_PAGE_TEMPLATES:
config["page_template"] = raw_config["page_template"]
return config
def resolve_quiz_box_config(obj: dict[str, Any]) -> dict[str, Any]:
raw = (obj.get("attributes") or {}).get("_quiz_box_config")
if not raw:
return dict(DEFAULT_QUIZ_BOX_CONFIG)
try:
parsed = json.loads(raw)
except (TypeError, ValueError):
return dict(DEFAULT_QUIZ_BOX_CONFIG)
return sanitize_quiz_box_config(parsed)