Supprime le type d'écran "document" et recentre le produit sur le jeu 2D
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:
@@ -18,7 +18,7 @@
|
||||
// de la quête au moment où le joueur parle au PNJ).
|
||||
|
||||
var QUEST_STATUS_LABELS = { nouvelle: 'Nouvelle', en_cours: 'En cours', terminee: 'Terminée' };
|
||||
var QUEST_RESULT_LABELS = { reussi: '✅ Réussi', echoue: '❌ Échoué' };
|
||||
var QUEST_RESULT_LABELS = { reussi: 'Réussi', echoue: 'Échoué' };
|
||||
var QUEST_STATUS_ORDER = ['nouvelle', 'en_cours', 'terminee'];
|
||||
|
||||
// Quêtes connues côté client — préremplies par QUESTS_JSON (voir
|
||||
@@ -52,7 +52,7 @@ function questListRowHtml(quest) {
|
||||
'<tr class="questListRow" onclick="openQuestFieldsModal(' + quest.id + ')">' +
|
||||
'<td><strong>' + (quest.title || '').replace(/</g, '<') + '</strong></td>' +
|
||||
'<td>' + (quest.objectif ? quest.objectif.replace(/</g, '<') : '<span class="hint">—</span>') + '</td>' +
|
||||
'<td><span class="questPill questPill-score">🏆 +' + (quest.recompense_score || 0) + '</span></td>' +
|
||||
'<td><span class="questPill questPill-score">+' + (quest.recompense_score || 0) + '</span></td>' +
|
||||
'<td><span class="questPill questPill-status questPill-status-' + quest.status + '">' + QUEST_STATUS_LABELS[quest.status] + '</span></td>' +
|
||||
'<td>' + resultatHtml + '</td>' +
|
||||
'<td class="fieldsTableActions">' +
|
||||
@@ -129,7 +129,27 @@ function ensureQuestFieldsModal() {
|
||||
// `onUse(quest)` optionnel : posé par collisionWizardChooseAction('quete', ...)
|
||||
// pour finaliser la règle de collision avec cette quête, affiche alors un
|
||||
// bouton "✓ Utiliser cette quête" dans les deux modales.
|
||||
//
|
||||
// Masque #collisionRuleModal s'il est ouvert, SANS effacer _collisionWizard
|
||||
// (contrairement à closeCollisionRuleModal(), static/js/scenes/
|
||||
// collision-rules-editor.js) : appelée depuis questPickerChoose()/
|
||||
// questPickerCreate() (assistant "+ Action" de l'onglet Collision, action
|
||||
// "quete" — voir renderQuestPickerStep()), CETTE modale et
|
||||
// #collisionRuleModal partagent le même z-index (.modal de base) — sans la
|
||||
// masquer, les deux restaient actives en même temps, empilées dans l'ordre
|
||||
// du DOM plutôt que l'une à la place de l'autre : le bouton "+ Action"
|
||||
// semblait ne plus rien faire ensuite (il rouvrait bien #collisionRuleModal,
|
||||
// mais caché derrière celle-ci restée active). _collisionWizard doit
|
||||
// SURVIVRE à ce masquage : "✓ Utiliser cette quête" (questUseThisQuest)
|
||||
// finalise la règle en cours via collisionWizardFinalizeAction(), qui en a
|
||||
// encore besoin (objectId, trigger, pendingInteragir pour "interagir").
|
||||
function _hideCollisionRuleModalKeepingWizardState() {
|
||||
var modal = document.getElementById('collisionRuleModal');
|
||||
if (modal) modal.classList.remove('is-active');
|
||||
}
|
||||
|
||||
function openQuestFieldsModal(questId, onUse) {
|
||||
_hideCollisionRuleModalKeepingWizardState();
|
||||
_questModalOnUse = onUse || null;
|
||||
_questModalCurrentId = questId;
|
||||
var modal = ensureQuestFieldsModal();
|
||||
@@ -179,16 +199,16 @@ function renderQuestFieldsModalBody(questId) {
|
||||
var body = document.getElementById('questFieldsModalBody');
|
||||
if (!quest || !body) return;
|
||||
var useBtn = _questModalOnUse
|
||||
? '<button type="button" class="collisionRuleAddBtn" onclick="questUseThisQuest(' + quest.id + ')">✓ Utiliser cette quête</button>'
|
||||
? '<button type="button" class="collisionRuleAddBtn" onclick="questUseThisQuest(' + quest.id + ')">Utiliser cette quête</button>'
|
||||
: '';
|
||||
body.innerHTML =
|
||||
'<div class="fieldRow questDialogueModalHeader" style="align-items:center; justify-content:space-between;">' +
|
||||
'<h2 class="title is-5" style="margin:0;">🗺️ ' + (quest.title || '').replace(/</g, '<') + '</h2>' +
|
||||
'<h2 class="title is-5" style="margin:0;">' + (quest.title || '').replace(/</g, '<') + '</h2>' +
|
||||
'<button type="button" class="button is-small" onclick="closeQuestFieldsModal()">✕</button>' +
|
||||
'</div>' +
|
||||
questFieldRowHtml(quest) +
|
||||
'<div class="fieldRow" style="justify-content:space-between; align-items:center; margin-top:6px;">' +
|
||||
'<button type="button" class="collisionRuleAddBtn" onclick="openQuestDialogueModal(' + quest.id + ')">💬 Ajouter les dialogues</button>' +
|
||||
'<button type="button" class="collisionRuleAddBtn" onclick="openQuestDialogueModal(' + quest.id + ')">Ajouter les dialogues</button>' +
|
||||
useBtn +
|
||||
'</div>';
|
||||
}
|
||||
@@ -212,7 +232,7 @@ function questSpeakerOptions(currentSpeaker) {
|
||||
function questDialogueBubbleHtml(line, status, index) {
|
||||
var kind = questBubbleKind(line.speaker);
|
||||
var speakerSelect = questSpeakerOptions(line.speaker).map(function (name) {
|
||||
return '<option value="' + name.replace(/"/g, '"') + '"' + (name === line.speaker ? ' selected' : '') + '>' + (name === 'Joueur' ? '🧑 Joueur' : '🗨️ ' + name) + '</option>';
|
||||
return '<option value="' + name.replace(/"/g, '"') + '"' + (name === line.speaker ? ' selected' : '') + '>' + (name === 'Joueur' ? 'Joueur' : name) + '</option>';
|
||||
}).join('');
|
||||
return (
|
||||
'<div class="questBubbleRow">' +
|
||||
@@ -262,9 +282,9 @@ function questQuestionBubbleHtml(line, status, index) {
|
||||
'<div class="questBubbleRow">' +
|
||||
'<div class="questBubble questBubble-question">' +
|
||||
'<div class="questBubbleHeader">' +
|
||||
'<label class="questQuestionHeaderField" title="Nombre de choix">🔢 <input type="number" min="2" max="4" value="' + line.choices.length + '" ' +
|
||||
'<label class="questQuestionHeaderField" title="Nombre de choix"><input type="number" min="2" max="4" value="' + line.choices.length + '" ' +
|
||||
'onchange="questSetQuestionChoiceCount(\'' + status + '\', ' + index + ', this.value)"></label>' +
|
||||
'<label class="questQuestionHeaderField" title="Récompense (points de score)">🏆 <input type="number" min="0" value="' + line.reward_amount + '" ' +
|
||||
'<label class="questQuestionHeaderField" title="Récompense (points de score)"><input type="number" min="0" value="' + line.reward_amount + '" ' +
|
||||
'onchange="questSetQuestionRewardAmount(\'' + status + '\', ' + index + ', this.value)"> pts</label>' +
|
||||
'<button type="button" class="questBubbleDelete" title="Supprimer" onclick="questDeleteBubble(\'' + status + '\', ' + index + ')">✕</button>' +
|
||||
'</div>' +
|
||||
@@ -288,8 +308,8 @@ function questDialogueColumnHtml(quest, status) {
|
||||
'<h4 class="questDialogueColumnTitle">' + QUEST_STATUS_LABELS[status] + '</h4>' +
|
||||
'<div class="questDialogueColumnBubbles" id="questDialogueColumn-' + status + '">' + bubblesHtml + '</div>' +
|
||||
'<div class="questDialogueColumnButtons">' +
|
||||
'<button type="button" class="collisionRuleAddBtn questDialogueAddBtn" onclick="questAddBubble(\'' + status + '\')">✚ Réplique</button>' +
|
||||
'<button type="button" class="collisionRuleAddBtn questDialogueAddBtn questQuestionAddBtn" onclick="questAddQuestion(\'' + status + '\')">❓ Question</button>' +
|
||||
'<button type="button" class="collisionRuleAddBtn questDialogueAddBtn" onclick="questAddBubble(\'' + status + '\')">Réplique</button>' +
|
||||
'<button type="button" class="collisionRuleAddBtn questDialogueAddBtn questQuestionAddBtn" onclick="questAddQuestion(\'' + status + '\')">Question</button>' +
|
||||
'</div>' +
|
||||
'</div>'
|
||||
);
|
||||
@@ -338,13 +358,13 @@ function renderQuestDialogueModalBody(questId) {
|
||||
if (!quest || !body) return;
|
||||
_questModalCurrentId = questId;
|
||||
var useBtn = _questModalOnUse
|
||||
? '<button type="button" class="collisionRuleAddBtn" onclick="questUseThisQuest(' + quest.id + ')">✓ Utiliser cette quête</button>'
|
||||
? '<button type="button" class="collisionRuleAddBtn" onclick="questUseThisQuest(' + quest.id + ')">Utiliser cette quête</button>'
|
||||
: '';
|
||||
body.innerHTML =
|
||||
'<div class="fieldRow questDialogueModalHeader" style="align-items:center; justify-content:space-between;">' +
|
||||
'<div style="display:flex; align-items:center; gap:10px;">' +
|
||||
'<button type="button" class="button is-small" onclick="questBackToFieldsModal(' + quest.id + ')" title="Retour à la fiche">←</button>' +
|
||||
'<h2 class="title is-5" style="margin:0;">🗺️ ' + (quest.title || '').replace(/</g, '<') + ' — dialogues</h2>' +
|
||||
'<h2 class="title is-5" style="margin:0;">' + (quest.title || '').replace(/</g, '<') + ' — dialogues</h2>' +
|
||||
'</div>' +
|
||||
'<div style="display:flex; gap:8px; align-items:center;">' + useBtn +
|
||||
'<button type="button" class="button is-small" onclick="closeQuestDialogueModal()">✕</button>' +
|
||||
@@ -520,7 +540,7 @@ function questPickerHtml(quests) {
|
||||
}
|
||||
return '<div class="collisionWizardChoices">' + quests.map(function (q, i) {
|
||||
return '<button type="button" class="collisionWizardChoice" style="animation-delay:' + (i * 40) + 'ms" onclick="questPickerChoose(' + q.id + ')">' +
|
||||
'<span class="emoji">🗺️</span><span>' + (q.title || '').replace(/</g, '<') + '</span>' +
|
||||
'<span>' + (q.title || '').replace(/</g, '<') + '</span>' +
|
||||
'</button>';
|
||||
}).join('') + '</div>';
|
||||
}
|
||||
@@ -531,7 +551,7 @@ function renderQuestPickerStep(onUse) {
|
||||
_questPickerOnUse = onUse;
|
||||
var body = collisionWizardBodyEl();
|
||||
body.innerHTML = '<p class="hint mb-2">Quelle quête déclencher ?</p><div id="questPickerList">Chargement…</div>' +
|
||||
'<button type="button" class="collisionRuleAddBtn mt-2" onclick="questPickerCreate()">✚ Nouvelle quête</button>';
|
||||
'<button type="button" class="collisionRuleAddBtn mt-2" onclick="questPickerCreate()">Nouvelle quête</button>';
|
||||
fetch('/game/' + GAME_SLUG + '/quests/list.json')
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (json) {
|
||||
@@ -556,6 +576,13 @@ function questPickerCreate() {
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
if (typeof QUESTS_JSON !== 'undefined') renderQuestListPage();
|
||||
});
|
||||
// Appelé directement (pas de DOMContentLoaded) : ce fichier est chargé par
|
||||
// un <script src> posé à la fin du contenu de la page — les éléments dont
|
||||
// on a besoin existent déjà à ce stade. Nécessaire aussi pour la
|
||||
// navigation pjax (voir static/pjax.js — runScriptsIn ré-exécute ce
|
||||
// script à chaque page swappée, mais DOMContentLoaded, lui, ne se
|
||||
// redéclenche jamais après le tout premier chargement réel) — sans ça,
|
||||
// l'onglet "🗺️ Quêtes" restait vide après un remplacement de <main> (ex.
|
||||
// suppression d'un objet de scène, voir scene_object_delete.py) qui ne
|
||||
// redéclenche jamais cet événement.
|
||||
if (typeof QUESTS_JSON !== 'undefined') renderQuestListPage();
|
||||
|
||||
Reference in New Issue
Block a user