Éditeur de quêtes : onglet à côté de Collision, pas une page à part
Build and deploy / test-python (push) Successful in 6m7s
Build and deploy / test-js (push) Successful in 59s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped

Corrige la mauvaise interprétation de la demande initiale : la page
dédiée "🗺️ Quêtes" (nav du jeu) est retirée au profit d'un nouvel
onglet "🗺️ Quêtes" DANS l'éditeur de scène 2D, juste à côté de
"🧩 Collision" — même liste tabulaire que l'onglet "📣 Événements"
(titre/objectif/récompense/statut/résultat par ligne, pas des cartes),
un clic sur une ligne ouvrant la modale d'arbre de dialogue déjà
construite. L'assistant "+ Action" de l'éditeur de collision ouvre
toujours cette même modale pour "Déclencher une quête".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
william
2026-09-02 20:46:27 +02:00
co-authored by Claude Sonnet 5
parent 51427b5297
commit 78c1aba8a0
10 changed files with 72 additions and 92 deletions
+44 -27
View File
@@ -1,56 +1,63 @@
// ---------- Éditeur de quêtes ----------
// Module partagé entre templates/quests.html (page dédiée : liste de
// cartes + "+ Nouvelle quête") et static/js/scenes/collision-rules-editor.js
// (l'assistant "+ Action" de l'éditeur de collision ouvre CETTE MÊME
// modale de dialogue quand l'auteur choisit "Déclencher une quête",
// plutôt qu'un simple champ texte pour un id). La modale (arbre de
// dialogue) est créée dynamiquement (ensureQuestDialogueModal) plutôt que
// dupliquée dans deux templates.
// Onglet "🗺️ Quêtes" à côté de "🧩 Collision" (templates/scene_edit.html,
// jamais une page à part) : une LISTE (mêmes tableau/lignes que l'onglet
// "📣 Événements") plutôt qu'une grille de cartes, chaque ligne portant
// toutes les infos de la quête + un clic dessus ouvrant la modale
// d'arbre de dialogue. Cette même modale est aussi ouverte depuis
// l'assistant "+ Action" de l'éditeur de collision (voir
// static/js/scenes/collision-rules-editor.js) quand l'auteur choisit
// "Déclencher une quête" — créée dynamiquement (ensureQuestDialogueModal)
// plutôt que dupliquée dans deux endroits.
//
// Une quête (voir db/quests/) : id, title, objectif, recompense_score,
// status (nouvelle/en_cours/terminee — statut COURANT, informatif),
// resultat (reussi/echoue), dialogues (une liste de répliques PAR
// STATUT — 3 colonnes dans l'éditeur : quel dialogue jouer selon l'état
// STATUT — 3 colonnes dans la modale : quel dialogue jouer selon l'état
// 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_STATUS_ORDER = ['nouvelle', 'en_cours', 'terminee'];
// Quêtes connues côté client — préremplies par QUESTS_JSON (quests.html)
// si présent, sinon vide tant qu'on n'a pas encore fetch (contexte
// éditeur de collision, voir questPickerEnsureLoaded).
// Quêtes connues côté client — préremplies par QUESTS_JSON (voir
// templates/scene_edit.html), toujours présentes puisque l'onglet
// Quêtes vit dans le même éditeur que l'onglet Collision qui en a
// besoin (collisionRuleActionSummary).
var QUESTS = (typeof QUESTS_JSON !== 'undefined') ? QUESTS_JSON : [];
function questById(questId) {
return QUESTS.find(function (q) { return q.id === questId; });
}
function questCardHtml(quest, i) {
var resultatHtml = quest.resultat ? '<span class="questPill questPill-resultat">' + QUEST_RESULT_LABELS[quest.resultat] + '</span>' : '';
function questListRowHtml(quest) {
var resultatHtml = quest.resultat ? '<span class="questPill questPill-resultat">' + QUEST_RESULT_LABELS[quest.resultat] + '</span>' : '<span class="hint">—</span>';
return (
'<div class="questCard" style="animation-delay:' + (i * 50) + 'ms" onclick="openQuestDialogueModal(' + quest.id + ')">' +
'<div class="questCardHeader">' +
'<h3 class="questCardTitle">' + (quest.title || '').replace(/</g, '&lt;') + '</h3>' +
'<span class="questPill questPill-status questPill-status-' + quest.status + '">' + QUEST_STATUS_LABELS[quest.status] + '</span>' +
'</div>' +
'<p class="questCardObjectif">' + (quest.objectif ? quest.objectif.replace(/</g, '&lt;') : '<span class="hint">Aucun objectif défini.</span>') + '</p>' +
'<div class="questCardFooter">' +
'<span class="questPill questPill-score">🏆 +' + (quest.recompense_score || 0) + '</span>' +
resultatHtml +
'</div>' +
'</div>'
'<tr class="questListRow" onclick="openQuestDialogueModal(' + quest.id + ')">' +
'<td><strong>' + (quest.title || '').replace(/</g, '&lt;') + '</strong></td>' +
'<td>' + (quest.objectif ? quest.objectif.replace(/</g, '&lt;') : '<span class="hint">—</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">' +
'<button type="button" class="button is-small is-danger" title="Supprimer" onclick="event.stopPropagation(); questDeleteFromList(' + quest.id + ')">🗑️</button>' +
'</td>' +
'</tr>'
);
}
function renderQuestListPage() {
var container = document.getElementById('questsGrid');
var container = document.getElementById('questsTabPanel');
if (!container) return;
var wrap = document.getElementById('questsListWrap');
if (!QUESTS.length) {
container.innerHTML = '<p class="hint">Aucune quête pour l\'instant — crée-en une avec le bouton ci-dessus.</p>';
wrap.innerHTML = '<p class="hint">Aucune quête pour l\'instant — crée-en une avec le bouton ci-dessus.</p>';
return;
}
container.innerHTML = QUESTS.map(questCardHtml).join('');
wrap.innerHTML =
'<div class="fieldsTableWrap"><table class="table is-fullwidth is-hoverable fieldsTable">' +
'<thead><tr><th>Titre</th><th>Objectif</th><th>Récompense</th><th>Statut</th><th>Résultat</th><th></th></tr></thead>' +
'<tbody>' + QUESTS.map(questListRowHtml).join('') + '</tbody>' +
'</table></div>';
}
function createQuestFromListPage() {
@@ -65,6 +72,16 @@ function createQuestFromListPage() {
});
}
function questDeleteFromList(questId) {
var quest = questById(questId);
if (!quest || !confirm('Supprimer la quête « ' + quest.title + ' » ? Toute règle de collision qui la référence perdra sa cible.')) return;
fetch('/game/' + GAME_SLUG + '/quests/' + questId + '/delete', { method: 'POST' })
.then(function () {
QUESTS = QUESTS.filter(function (q) { return q.id !== questId; });
renderQuestListPage();
});
}
// ---------- Modale "arbre de dialogue" ----------
// Créée une seule fois (au premier appel), réutilisée ensuite — partagée
// entre la page dédiée et l'assistant "+ Action" de l'éditeur de
+4 -13
View File
@@ -760,20 +760,11 @@ body.builderBody > main.content{ flex:1 1 auto; min-height:0; overflow:hidden; d
.collisionInteractBubble{ display:none; position:absolute; left:50%; bottom:100%; transform:translateX(-50%); margin-bottom:6px; background:#111; color:#fff; border-radius:10px; padding:4px 10px; font-size:12px; white-space:nowrap; pointer-events:none; z-index:20; }
/* ---- Éditeur de quêtes (templates/quests.html + modale de dialogue
partagée avec l'assistant "+ Action" de l'éditeur de collision, voir
/* ---- Éditeur de quêtes (onglet "🗺️ Quêtes" à côté de "🧩 Collision",
templates/scene_edit.html + modale de dialogue partagée avec
l'assistant "+ Action" de l'éditeur de collision, voir
static/js/quests/quest-editor.js) ---- */
.questsGrid{ display:grid; grid-template-columns:repeat(auto-fill, minmax(280px, 1fr)); gap:18px; margin-top:14px; }
.questCard{
background:linear-gradient(165deg, var(--panel), var(--panel2)); border:1px solid var(--border);
border-radius:16px; padding:16px; cursor:pointer; box-shadow:0 6px 18px rgba(0,0,0,.18);
animation:collisionCardIn .35s ease backwards; transition:transform .18s ease, box-shadow .18s ease;
}
.questCard:hover{ transform:translateY(-3px); box-shadow:0 10px 26px rgba(0,0,0,.28); border-color:var(--accent); }
.questCardHeader{ display:flex; align-items:center; justify-content:space-between; gap:8px; margin-bottom:8px; }
.questCardTitle{ margin:0; font-size:15px; font-weight:700; }
.questCardObjectif{ font-size:12.5px; color:var(--forge-text-muted, #8b95a7); margin-bottom:12px; min-height:1.4em; }
.questCardFooter{ display:flex; gap:8px; flex-wrap:wrap; }
.questListRow{ cursor:pointer; }
.questPill{ display:inline-flex; align-items:center; border-radius:999px; padding:3px 10px; font-size:11.5px; font-weight:700; }
.questPill-score{ background:rgba(242,153,74,.18); color:#f2994a; }
.questPill-resultat{ background:rgba(23,162,184,.18); color:#17a2b8; }