Supprime le type d'écran "document" et recentre le produit sur le jeu 2D
Build and deploy / test-python (push) Successful in 9m41s
Build and deploy / test-js (push) Successful in 1m0s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped

Le document de cadrage produit cible des formateurs non techniques créant
des serious games/quiz gamifiés — l'éditeur générique "document" (blocs de
logique en nœuds, timeline d'animation, définitions d'objets/relations,
templates réutilisables) est une complexité hors cible que l'effort
d'ingénierie récent avait déjà abandonnée au profit du jeu_2d.

- Onboarding : ne garde que le parcours "RPG" (jeu_2d), retire
  Quiz/Embranchement/Créer mon jeu de A à Z (tous document-only)
- Suppression en bloc des modules exclusifs au document : routes/elements,
  routes/element_types, routes/objects, routes/legacy_actions,
  screens/elements, screens/element_types, screens/widgets,
  screens/legacy_actions, le rendu render_element_html.py et son cluster,
  templates/screen_edit.html, templates/game_dashboard.html,
  flow-editor.js/tabs-and-blocks.js/animation-timeline.js
- Dashboard toujours simplifié (un seul mode possible désormais)
- Tests document-only supprimés, tests de logique partagée (flow,
  événements personnalisés, animations) retargetés sur des écrans jeu_2d
- Aucune régression jeu_2d : 299 tests passent

Carte d'onboarding retravaillée : argumentaire RH non technique (liste à
coche, badge "Compatible LMS"), taille et interaction de retournement
ajustées.
This commit is contained in:
william
2026-09-04 20:56:32 +02:00
parent 00f7191a8b
commit 50835a18e2
221 changed files with 1239 additions and 12245 deletions
+60 -53
View File
@@ -5,22 +5,6 @@
{% block extra_head %}
<link rel="stylesheet" href="{{ url_for('static', filename='vendor/fonts/fonts.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='vendor/animate.min.css') }}">
<style>
/* render_scene_object.py positionne son <img> en absolu (left/top/width/
height en px) pour s'afficher correctement en enfant DIRECT de
.playScreen.playScene (voir templates/play.html) — dans le canevas de
CET éditeur, cette même balise est nichée dans .canvasElementInner,
lui-même déjà positionné/dimensionné par .canvasElement (voir plus
bas) : sans ce correctif, l'image se positionnait EN PLUS depuis
#canvas (son ancêtre positionné le plus proche), en double du
décalage déjà appliqué par .canvasElement. */
.canvasElementInner > img{ position:static !important; left:auto !important; top:auto !important; width:100% !important; height:100% !important; z-index:auto !important; }
/* Même correctif que ci-dessus, pour le widget "💬 Boîte de dialogue"
(screens/scenes/render_scene_object.py, un <div>, pas un <img>). */
.canvasElementInner > .dialogueBoxWidget, .canvasElementInner > .quizBoxWidget, .canvasElementInner > .scoreWidget{
position:static !important; left:auto !important; top:auto !important; width:100% !important; height:100% !important; z-index:auto !important;
}
</style>
{% endblock %}
{% block content %}
<!-- Fondations multi-éditeurs — éditeur de scène 2D (jeux "jeu_2d", voir
@@ -29,15 +13,27 @@
tel quel pour l'éditeur document) a été retiré de CET éditeur à la
demande de l'utilisateur, en prévision d'un éditeur dédié aux scènes
2D (collision/périmètre -> action), pas encore construit. -->
<p class="hint" style="background:var(--panel); border:1px solid var(--border); border-radius:8px; padding:10px 14px; margin:10px 0; display:flex; align-items:center; justify-content:space-between; gap:12px; flex-wrap:wrap;">
<span>🎮 Jeu 2D — scène à taille fixe.</span>
<!-- "Objets"/"Propriétés" (voir panelLeftFloat/propsPanelFloat plus bas) :
posés ICI, dans la barre d'outils de la scène, plutôt qu'en position
fixe flottant au-dessus de la page (ancien "🧩 Objets"/"⚙️ Propriétés"
tout en haut de l'écran) — ils y chevauchaient "Mes écrans"/"Jouer"/
"Exporter" (voir base.html) une fois les panneaux fermés. Remplace la
simple mention "Jeu 2D — scène à taille fixe." -->
<!-- 3 zones INDÉPENDANTES (grid, jamais un flex space-between) : "Objets"/
"Propriétés" ne sont visibles que panneau fermé (display:none sinon,
voir .floatPanelReopenBtn) — avec un flex classique, la zone caméra
se retrouve seule dans la rangée la plupart du temps et se déplace
selon ce qui l'entoure. Un grid à 3 colonnes fixe sa position
(toujours centrée) sans dépendre des deux autres zones. -->
<p class="hint" style="background:var(--panel); border:1px solid var(--border); border-radius:8px; padding:10px 14px; margin:10px 0; display:grid; grid-template-columns:1fr auto 1fr; align-items:center; gap:12px;">
<span><button type="button" id="panelLeftReopenBtn" class="button is-small floatPanelReopenBtn" onclick="openLeftPanel()" title="Rouvrir le panneau des objets">Objets</button></span>
<!-- Taille de la CAMÉRA (scene_width/scene_height) — TOUJOURS
modifiable ici, directement en pixels (demande explicite : "la
caméra a la même taille [que le rectangle de prévisualisation],
c'est tout"), pas seulement suggérée automatiquement quand un
fond dépasse (voir toujours "🎥 Champ de la caméra" plus bas si
c'est le cas, purement informatif dans ce cas). -->
<span style="display:flex; align-items:center; gap:6px;">
<span style="display:flex; align-items:center; gap:6px; justify-self:center;">
<label class="hint" style="margin:0;">Caméra (px)
<input type="number" id="scene-camera-w" class="input is-small" style="width:80px;" min="1" value="{{ screen.scene_width }}">
×
@@ -45,12 +41,12 @@
</label>
<button type="button" class="button is-small" onclick="applyCameraSize()">Appliquer</button>
</span>
<span style="justify-self:end;"><button type="button" id="propsPanelReopenBtn" class="button is-small floatPanelReopenBtn" onclick="openPropsPanel()" title="Rouvrir le panneau de propriétés">Propriétés</button></span>
</p>
<div class="builder3" id="builder3">
{% set obj = objects|selectattr("id", "equalto", selected_id)|first if selected_id else none %}
<button type="button" id="panelLeftReopenBtn" class="button is-small floatPanelReopenBtn" onclick="openLeftPanel()" title="Rouvrir le panneau des objets">🧩 Objets</button>
<div class="floatPanel floatPanel--left" id="panelLeftFloat">
<div class="floatPanelHeader" data-drag-panel="panelLeftFloat" data-drag-key="left">
<span class="floatPanelTitle">⋮⋮ Objets de cette scène</span>
@@ -61,7 +57,7 @@
<div class="control is-expanded"><input class="input is-small" type="text" name="name" value="{{ screen.name }}" required></div>
<div class="control"><button type="submit" class="button is-small">Renommer</button></div>
</form>
<a class="button is-small is-primary is-fullwidth mb-2" href="{{ url_for('game_play', slug=game.slug) }}" target="_blank">▶️ Jouer depuis le début</a>
<a class="button is-small is-primary is-fullwidth mb-2" href="{{ url_for('game_play', slug=game.slug) }}" target="_blank">Jouer depuis le début</a>
<hr style="border-color:var(--border); margin:10px 0;">
<!-- "Objets de cette scène" (arborescence des objets déjà posés)
@@ -74,7 +70,7 @@
personnage-preview.js, réutilisé tel quel : ne dépend que de
data-personnage, pas de la table d'origine). -->
<details class="propGroup" open>
<summary>🎭 Personnages</summary>
<summary>Personnages</summary>
<div class="propGroupBody">
<div class="iconGallery personnageGallery">
{% for slug_c, character in sprite_library_families %}
@@ -93,7 +89,7 @@
grande que la scène, la caméra la suit en défilant derrière le
personnage (voir static/js/play/personnage-controller.js). -->
<details class="propGroup">
<summary>🏞️ Images de fond</summary>
<summary>Images de fond</summary>
<div class="propGroupBody">
<div class="iconGallery personnageGallery">
{% for slug_b, background in background_library.items() %}
@@ -114,12 +110,12 @@
l'auteur ne fait que la poser où il veut et la styliser (panneau
de propriétés de droite, une fois sélectionnée). -->
<details class="propGroup">
<summary>🖥️ Interface</summary>
<summary>Interface</summary>
<div class="propGroupBody">
<button type="button" class="button is-small is-fullwidth mb-2" onclick="addSceneObject('dialogue_box')">💬 Boîte de dialogue</button>
<button type="button" class="button is-small is-fullwidth mb-2" onclick="addSceneObject('quiz_box')">❓ Boîte à quiz</button>
<button type="button" class="button is-small is-fullwidth" onclick="addSceneObject('score_widget')">🏆 Score</button>
<p class="help">Boîte de dialogue/quiz : gérées automatiquement par le moteur (voir l'onglet "🗺️ Quêtes") — position et style réglables une fois posées. Score : affiche en continu les points gagnés.</p>
<button type="button" class="button is-small is-fullwidth mb-2" onclick="addSceneObject('dialogue_box')">Boîte de dialogue</button>
<button type="button" class="button is-small is-fullwidth mb-2" onclick="addSceneObject('quiz_box')">Boîte à quiz</button>
<button type="button" class="button is-small is-fullwidth" onclick="addSceneObject('score_widget')">Score</button>
<p class="help">Boîte de dialogue/quiz : gérées automatiquement par le moteur (voir l'onglet "Quêtes") — position et style réglables une fois posées. Score : affiche en continu les points gagnés.</p>
</div>
</details>
</div>
@@ -127,10 +123,18 @@
<div class="builderCanvasArea">
<div class="builderTabs" id="builderTabs">
<button type="button" class="builderTabBtn active" data-tab="screen" onclick="switchBuilderTab('screen')">🖥️ Scène</button>
<button type="button" class="builderTabBtn" data-tab="collision-rules" onclick="switchBuilderTab('collision-rules')">🧩 Collision</button>
<button type="button" class="builderTabBtn" data-tab="quests" onclick="switchBuilderTab('quests')">🗺️ Quêtes</button>
<button type="button" class="builderTabBtn" data-tab="events" onclick="switchBuilderTab('events')">📣 Événements</button>
<button type="button" class="builderTabBtn active" data-tab="screen" onclick="switchBuilderTab('screen')">Scène</button>
<button type="button" class="builderTabBtn" data-tab="collision-rules" onclick="switchBuilderTab('collision-rules')">Collision</button>
<button type="button" class="builderTabBtn" data-tab="quests" onclick="switchBuilderTab('quests')">Quêtes</button>
{% if game.onboarding_type != 'rpg' %}
<!-- Événements personnalisés (voir db.list_custom_events) : aucun
moyen de les DÉCLENCHER depuis un jeu RPG pour l'instant —
l'assistant "+ Action" de l'onglet Collision exclut
délibérément le type "evenement" (voir COLLISION_WIZARD_
ACTION_TYPES, static/js/scenes/collision-rules-editor.js) —
demande explicite : cet onglet n'a donc aucune utilité ici. -->
<button type="button" class="builderTabBtn" data-tab="events" onclick="switchBuilderTab('events')">Événements</button>
{% endif %}
</div>
<div class="builderTabPanel active" data-tab-panel="screen">
@@ -187,7 +191,7 @@
règle sans recharger la page. -->
<div class="builderTabPanel" id="collisionRulesTabPanel" data-tab-panel="collision-rules">
<div class="dashPanelHeader">
<h2>🧩 Collision</h2>
<h2>Collision</h2>
</div>
<p class="hint">Pour chaque objet (jamais le joueur lui-même), choisis ce qui doit se passer à son contact — la carte se construit au fil de tes choix, comme des pièces qui s'emboîtent.</p>
<div id="collisionRulesCards" class="collisionRulesCards"></div>
@@ -219,13 +223,14 @@
convention que #collisionRulesCards ci-dessus. -->
<div class="builderTabPanel" id="questsTabPanel" data-tab-panel="quests">
<div class="dashPanelHeader">
<h2>🗺️ Quêtes</h2>
<h2>Quêtes</h2>
<button type="button" class="button is-small primary" onclick="createQuestFromListPage()">+ Créer une quête</button>
</div>
<p class="hint">Titre, objectif, récompense, statut, résultat — clique sur une ligne pour écrire son enchaînement de dialogue.</p>
<div id="questsListWrap"></div>
</div>
{% if game.onboarding_type != 'rpg' %}
<div class="builderTabPanel" id="eventsTabPanel" data-tab-panel="events">
<div class="dashPanelHeader">
<h2>Événements personnalisés</h2>
@@ -256,7 +261,7 @@
{% set usages = custom_event_usages.get(ev.id, []) %}
{% if usages %}
{% for u in usages %}
<a href="{{ url_for('screen_edit', slug=game.slug, screen_id=u.screen_id) }}" class="linkLikeButton" title="{{ 'Écouté ici' if u.role == 'ecoute' else 'Déclenché ici' }}">{{ '🔔' if u.role == 'ecoute' else '📣' }} {{ u.screen_name }}{{ ' (modèle)' if u.is_template else '' }}</a>{{ ', ' if not loop.last else '' }}
<a href="{{ url_for('screen_edit', slug=game.slug, screen_id=u.screen_id) }}" class="linkLikeButton" title="{{ 'Écouté ici' if u.role == 'ecoute' else 'Déclenché ici' }}">{{ u.screen_name }}{{ ' (modèle)' if u.is_template else '' }}</a>{{ ', ' if not loop.last else '' }}
{% endfor %}
{% else %}
<span class="hint">Inutilisé</span>
@@ -279,10 +284,12 @@
<p class="hint">Aucun événement pour l'instant — crée le premier ci-dessus.</p>
{% endif %}
</div>
{% endif %}
</div>
<!-- Panneau droit : propriétés de l'objet sélectionné. -->
<button type="button" id="propsPanelReopenBtn" class="button is-small floatPanelReopenBtn" onclick="openPropsPanel()" title="Rouvrir le panneau de propriétés">⚙️ Propriétés</button>
<!-- Panneau droit : propriétés de l'objet sélectionné. Son bouton de
réouverture est maintenant dans la barre d'outils tout en haut
(voir #propsPanelReopenBtn plus haut), pas ici. -->
<div class="floatPanel floatPanel--right" id="propsPanelFloat">
<div class="floatPanelHeader" data-drag-panel="propsPanelFloat" data-drag-key="props">
<span class="floatPanelTitle">⋮⋮ Propriétés</span>
@@ -295,10 +302,8 @@
{% set _WIDGET_ICONS = {'dialogue_box': '💬', 'quiz_box': '❓', 'score_widget': '🏆'} %}
{% set _WIDGET_LABELS = {'dialogue_box': 'Boîte de dialogue', 'quiz_box': 'Boîte à quiz', 'score_widget': 'Score'} %}
<div class="fieldRow" style="align-items:center; justify-content:space-between;">
<h2 class="subtitle is-6" style="margin:0;">{{ '🏃' if obj.kind == 'personnage' else _WIDGET_ICONS.get(obj.kind, '🖼️') }} {{ obj.name or ('Personnage' if obj.kind == 'personnage' else _WIDGET_LABELS.get(obj.kind, 'Décor')) }}</h2>
<form method="post" action="{{ url_for('scene_object_delete', slug=game.slug, object_id=obj.id) }}" onsubmit="return confirm('Supprimer cet objet ?');">
<button type="submit" class="button is-small is-danger">🗑️</button>
</form>
<h2 class="subtitle is-6" style="margin:0;">{{ obj.name or ('Personnage' if obj.kind == 'personnage' else _WIDGET_LABELS.get(obj.kind, 'Décor')) }}</h2>
<button type="button" class="button is-small is-danger" onclick="if (confirm('Supprimer cet objet ?')) deleteSceneObject({{ obj.id }});">🗑️</button>
</div>
{% if obj.kind != 'dialogue_box' %}
@@ -310,7 +315,7 @@
quest-editor.js) : un dialogue peut mettre en scène n'importe
quel objet, pas seulement un personnage. -->
<details class="propGroup" open>
<summary>ℹ️ Informations</summary>
<summary>Informations</summary>
<div class="propGroupBody">
<div class="field">
<label class="label is-small">Nom de l'objet</label>
@@ -355,7 +360,7 @@
texte, couleur de chaque zone (header/body/footer, "header_bg"
sert de fond unique pour "🏆 Score", qui n'a pas de body/footer). -->
<details class="propGroup" open>
<summary>🎨 Style</summary>
<summary>Style</summary>
<div class="propGroupBody" data-element-id="{{ obj.id }}">
<div class="field">
<label class="label is-small">Police</label>
@@ -419,7 +424,7 @@
static/js/play/personnage-controller.js), ce panneau n'aurait
donc aucun effet. -->
<details class="propGroup">
<summary>🧱 Collision</summary>
<summary>Collision</summary>
<div class="propGroupBody" id="collisionPanelBody" data-element-id="{{ obj.id }}">
<div class="field">
<div class="control">
@@ -458,13 +463,13 @@
{% if obj.kind == 'personnage' %}
<details class="propGroup" open>
<summary>🎭 Personnage</summary>
<summary>Personnage</summary>
<div class="propGroupBody" id="personnagePanelBody" data-element-id="{{ obj.id }}">
<!-- Rôle (voir screens/rendering/personnage_role.py) : seul un
personnage "Joueur" est déplacé/animé automatiquement au
clavier et suivi par la caméra — "Ennemi"/"PNJ" restent
immobiles tant qu'aucune logique de flow ne les pilote. -->
<div class="field"><label class="label is-small">🏷️ Rôle</label>
<div class="field"><label class="label is-small">Rôle</label>
<div class="control">
<div class="select is-small">
<select id="personnage-role" onchange="saveSceneObjectRole(this.value)">
@@ -504,8 +509,8 @@
le moindre nœud de flow à poser — ce panneau ne fait que
permettre de les remapper/bloquer un axe. -->
<details class="propGroup">
<summary>🕹️ Commandes</summary>
<div class="propGroupBody" id="personnageCommandsBody" data-element-id="{{ obj.id }}">
<summary>Commandes</summary>
<div class="propGroupBody" id="personnageCommandsBody" data-element-id="{{ obj.id }}" data-commands-extra='{{ (personnage_commands.extra if personnage_commands else [])|tojson }}'>
<p class="help">Ce personnage se déplace et s'anime automatiquement avec ces touches, sans rien à poser dans la logique. Clique un champ puis appuie sur la touche voulue.</p>
<div class="field is-grouped">
<div class="control"><label class="label is-small">Haut</label>
@@ -539,10 +544,13 @@
<!-- Touches supplémentaires (sauter, attaquer...) : au-delà des 4
touches de déplacement et de "Interagir" ci-dessus, chacune
liée à UNE pose au choix du personnage (voir
ELEMENT_ANIMATIONS_MAP/PERSONNAGE_COMMANDS_EXTRA, déjà
connues de scene-editor.js) — construites en JS, jamais en
Jinja : le nombre de lignes change dynamiquement (+ Ajouter
/ ×). -->
ELEMENT_ANIMATIONS_MAP, déjà connue de scene-editor.js, et
data-commands-extra ci-dessus, LU sur #personnageCommandsBody
plutôt que via une variable globale — voir
initPersonnageCommandsPanel(), rappelée après un changement
de sélection sans rechargement) — construites en JS, jamais
en Jinja : le nombre de lignes change dynamiquement
(+ Ajouter / ×). -->
<div class="field"><label class="label is-small">Animations supplémentaires (sauter, attaquer...)</label>
<div id="personnageCommandsExtra"></div>
<button type="button" class="button is-small" onclick="addPersonnageCommandExtraRow()">+ Ajouter une touche</button>
@@ -588,7 +596,6 @@
var ANIMATE_CSS_LABELS = {{ animate_css_labels_json|safe }};
var SPRITE_LIBRARY = {{ sprite_library_json|safe }};
var ELEMENT_ANIMATIONS_MAP = {{ element_animations_map_json|safe }};
var PERSONNAGE_COMMANDS_EXTRA = {{ (personnage_commands.extra if personnage_commands else [])|tojson }};
var COLLISION_RULE_OBJECTS = {{ collision_rule_objects_json|safe }};
var COLLISION_ACTION_TYPES = {{ collision_action_types|tojson }};
var COLLISION_TRIGGER_TYPES = {{ collision_trigger_types|tojson }};