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
+18 -11
View File
@@ -52,16 +52,23 @@
route pour tous les comptes — routes/games/game_dashboard.py y rend
une version simplifiée (juste les écrans) pour un type "restreint"
(quiz/embranchement/rpg), la version complète pour "custom". -->
<a href="{{ url_for('game_dashboard', slug=game.slug) }}" class="gameNavLink {{ 'active' if request.path == '/game/' ~ game.slug else '' }}" title="{{ 'Écrans du jeu' if game.onboarding_type in ('quiz', 'embranchement', 'rpg') else 'Écrans, objets, templates, variables' }}">📊 Tableau de bord</a>
<a href="{{ url_for('game_play', slug=game.slug) }}" target="_blank" class="gameNavLink" title="Aperçu jouable du jeu (une seule partie partagée, pour la conception)">▶️ Jouer</a>
<!-- Phase 1.2 (feuille de route produit) : seul export retenu — paquet
STATIQUE (aucun Python/Flask embarqué, voir
publish/build_scorm_package.py), pensé pour un import dans le LMS
du client (Moodle...). L'export exécutable Windows ("Publier",
publish/build_package.py) et l'export en ligne (route publique
/jouer/<slug>, routes/public_play/) ont été retirés, jugés
redondants avec celui-ci. -->
<button type="button" class="gameNavLink" onclick="window.forgeOpenScormModal && window.forgeOpenScormModal()" title="Exporte le jeu en paquet Web/SCORM importable dans un LMS">🎓 Exporter (Web/SCORM)</button>
<a href="{{ url_for('game_dashboard', slug=game.slug) }}" class="gameNavLink {{ 'active' if request.path == '/game/' ~ game.slug else '' }}" title="Écrans du jeu">Mes écrans</a>
<!-- "Jouer"/"Exporter" groupés à droite (gameNavRight, margin-left:auto
en CSS) et distingués par COULEUR (orange/vert, voir .gameNavLink--
play/--export dans static/style.css) — demande explicite, ce sont
des ACTIONS (lancer une partie, produire un paquet), pas des pages
de gestion comme "Mes écrans" à gauche. -->
<div class="gameNavRight">
<a href="{{ url_for('game_play', slug=game.slug) }}" target="_blank" class="gameNavLink gameNavLink--play" title="Aperçu jouable du jeu (une seule partie partagée, pour la conception)">Jouer</a>
<!-- Phase 1.2 (feuille de route produit) : seul export retenu — paquet
STATIQUE (aucun Python/Flask embarqué, voir
publish/build_scorm_package.py), pensé pour un import dans le LMS
du client (Moodle...). L'export exécutable Windows ("Publier",
publish/build_package.py) et l'export en ligne (route publique
/jouer/<slug>, routes/public_play/) ont été retirés, jugés
redondants avec celui-ci. -->
<button type="button" class="gameNavLink gameNavLink--export" onclick="window.forgeOpenScormModal && window.forgeOpenScormModal()" title="Exporte le jeu en paquet Web/SCORM importable dans un LMS">Exporter (Web/SCORM)</button>
</div>
</nav>
{% endif %}
</div>
@@ -112,7 +119,7 @@
<div class="passwordStrengthBar" style="margin-top:10px;"><div class="passwordStrengthFill strong" id="scormProgressFill" style="width:100%; animation:forgePublishPulse 1.2s ease-in-out infinite;"></div></div>
</div>
<div id="scormError" class="notification is-danger" style="display:none;"></div>
<div id="scormDone" class="notification is-success" style="display:none;">✅ Téléchargement lancé — importe directement le .zip dans ton LMS.</div>
<div id="scormDone" class="notification is-success" style="display:none;">Téléchargement lancé — importe directement le .zip dans ton LMS.</div>
<div class="field mt-4" style="display:flex; justify-content:flex-end;">
<button type="button" class="button is-small" onclick="window.forgeCloseScormModal()">Fermer</button>
</div>
File diff suppressed because it is too large Load Diff
+8 -11
View File
@@ -19,21 +19,18 @@
(scene_width/scene_height), pas de preview_aspect (propre aux écrans
"document", sans rapport avec une scène "jeu_2d"). -#}
{% macro screen_preview(s, preview) %}
{% if preview.kind == 'jeu_2d' %}
<div class="screenPreviewBox" style="aspect-ratio:{{ s.scene_width }} / {{ s.scene_height }};">
<div class="screenPreviewScene" style="width:{{ s.scene_width }}px; height:{{ s.scene_height }}px; --scene-w:{{ s.scene_width }}px;">
<!-- Mis à l'échelle sur le MONDE entier (preview.world_width/height, voir
screens.resolve_scene_world_size) — jamais scene_width/scene_height
(la caméra nominale) : un "fond" plus grand que la caméra dépasse
cette dernière (voir add_scene_object.py/scene_edit_view.py), et la
miniature n'en montrait alors que le coin haut-gauche au lieu de
toute la scène. -->
<div class="screenPreviewBox" style="aspect-ratio:{{ preview.world_width }} / {{ preview.world_height }};">
<div class="screenPreviewScene" style="width:{{ preview.world_width }}px; height:{{ preview.world_height }}px; --scene-w:{{ preview.world_width }}px;">
{% for obj in preview.objects %}{{ obj.rendered_html|safe }}{% endfor %}
</div>
{% if not preview.objects %}<div class="screenPreviewEmpty">Écran vide</div>{% endif %}
</div>
{% else %}
<div class="screenPreviewBox" style="aspect-ratio:{{ (s.preview_aspect or '9/16')|replace('/', ' / ') }};">
{% for el in preview.elements if not el.parent_id %}
<div style="{{ el|elstyle }}"><div style="{{ el|eltransform }}">{{ el.rendered_html|safe }}</div></div>
{% endfor %}
{% if not preview.elements %}<div class="screenPreviewEmpty">Écran vide</div>{% endif %}
</div>
{% endif %}
{% endmacro %}
<div class="dashPanelHeader">
+3 -3
View File
@@ -3,7 +3,7 @@
{% block body_class %}objectEditBody{% endblock %}
{% block content_class %}content-wide onboardingPage{% endblock %}
{% block content %}
{% if error %}<p class="hint onboardingError">⚠️ {{ error }}</p>{% endif %}
{% if error %}<p class="hint onboardingError">{{ error }}</p>{% endif %}
<form method="post" action="{{ url_for('onboarding_new') }}" id="onboardingForm">
<input type="hidden" name="onboarding_type" id="onboardingTypeHidden">
@@ -22,7 +22,7 @@
<span class="onboardingCardLabel">{{ info.label }}</span>
</span>
<span class="onboardingCardFace onboardingCardBack">
<span class="onboardingCardDesc">{{ info.description }}</span>
<span class="onboardingCardDesc">{{ info.description|safe }}</span>
</span>
</span>
</button>
@@ -35,7 +35,7 @@
<div class="onboardingNameBox">
<label class="onboardingTitle" for="onboardingNameInput">Quel est le nom de votre jeu ?</label>
<input type="text" name="name" id="onboardingNameInput" class="onboardingNameInput" placeholder="ex. CyberQuest RGPD" autocomplete="off">
<button type="submit" id="onboardingSubmitBtn" class="primary onboardingSubmitBtn" disabled>Créer mon jeu 🚀</button>
<button type="submit" id="onboardingSubmitBtn" class="primary onboardingSubmitBtn" disabled>Créer mon jeu</button>
</div>
</section>
+1 -1
View File
@@ -75,5 +75,5 @@
</div>
</div>
</div>
<button type="button" class="button is-small is-danger clauseRemoveBtn">🗑️ Retirer cette condition</button>
<button type="button" class="button is-small is-danger clauseRemoveBtn">Retirer cette condition</button>
</div>
+2 -13
View File
@@ -72,11 +72,9 @@
<div id="playRoot">
<div id="playFrame">
{% for s in screens_data %}
{% if s.kind == 'jeu_2d' %}
<!-- Fusion des moteurs — écran "jeu_2d" : objets déjà entièrement
<!-- Un seul moteur d'écran désormais (jeu_2d) : objets déjà entièrement
positionnés en pixels absolus par render_scene_object.py (voir
screens/scenes/), pas de wrapper .playElement/elstyle (spécifiques
au %/DOM d'un écran "document") : juste le HTML tel quel. -->
screens/scenes/) — juste le HTML tel quel. -->
<div class="playScreen playScene" data-screen-id="{{ s.id }}" data-kind="jeu_2d" style="width:{{ s.scene_width }}px; height:{{ s.scene_height }}px;">
<!-- .sceneWorld : le "monde" (voir static/style.css et
static/js/play/personnage-controller.js) — taille par défaut 100%
@@ -93,15 +91,6 @@
{% for obj in s.elements if obj.kind in ('dialogue_box', 'quiz_box', 'score_widget') %}{{ obj.rendered_html|safe }}{% endfor %}
</div>
</div>
{% else %}
<div class="playScreen" data-screen-id="{{ s.id }}" data-kind="document" data-aspect="{{ s.preview_aspect or '9/16' }}">
{% for el in s.elements if not el.parent_id %}
<div class="playElement" data-el-id="{{ el.id }}" data-anim-target="{{ el.id }}" style="{{ el|elstyle }}">
<div class="playElementInner" style="{{ el|eltransform }}">{{ el.rendered_html|safe }}</div>
</div>
{% endfor %}
</div>
{% endif %}
{% endfor %}
{% if not screens_data %}
<div id="emptyState">Aucun écran défini pour l'instant — retourne dans l'éditeur pour en créer un.</div>
+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 }};
File diff suppressed because it is too large Load Diff