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
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>
102 lines
4.7 KiB
Python
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)
|