Quêtes : dialogue dans une modale à part, en plein écran
Sépare la modale unique en deux étapes distinctes demandées par
l'utilisateur : #questFieldsModal ("je crée" — titre/objectif/
récompense/statut/résultat, taille normale) puis, sur un bouton dédié
"💬 Ajouter les dialogues", #questDialogueModal ("j'ajoute les
dialogues" — les 3 colonnes de répliques SEULES, occupant tout
l'écran). Un bouton "←" ramène à la fiche ; "✓ Utiliser cette quête"
reste disponible dans les deux, pour l'intégration avec l'éditeur de
collision.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
78c1aba8a0
commit
390ee831ca
@@ -1,13 +1,15 @@
|
||||
// ---------- Éditeur de quêtes ----------
|
||||
// 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.
|
||||
// "📣 Événements") plutôt qu'une grille de cartes. Un clic sur une ligne
|
||||
// ouvre la modale de FICHE (titre/objectif/récompense/statut/résultat —
|
||||
// "je crée"), qui ouvre à son tour, sur un bouton dédié, la modale
|
||||
// PLEIN ÉCRAN d'arbre de dialogue ("j'ajoute les dialogues") — deux
|
||||
// étapes séparées, jamais le même formulaire. Les deux modales sont
|
||||
// aussi ouvertes 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éées dynamiquement (ensureQuest*Modal)
|
||||
// plutôt que dupliquées dans deux endroits.
|
||||
//
|
||||
// Une quête (voir db/quests/) : id, title, objectif, recompense_score,
|
||||
// status (nouvelle/en_cours/terminee — statut COURANT, informatif),
|
||||
@@ -32,7 +34,7 @@ function questById(questId) {
|
||||
function questListRowHtml(quest) {
|
||||
var resultatHtml = quest.resultat ? '<span class="questPill questPill-resultat">' + QUEST_RESULT_LABELS[quest.resultat] + '</span>' : '<span class="hint">—</span>';
|
||||
return (
|
||||
'<tr class="questListRow" onclick="openQuestDialogueModal(' + quest.id + ')">' +
|
||||
'<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>' +
|
||||
@@ -68,7 +70,7 @@ function createQuestFromListPage() {
|
||||
.then(function (json) {
|
||||
QUESTS.unshift(json.quest);
|
||||
renderQuestListPage();
|
||||
openQuestDialogueModal(json.quest.id);
|
||||
openQuestFieldsModal(json.quest.id);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -82,22 +84,28 @@ function questDeleteFromList(questId) {
|
||||
});
|
||||
}
|
||||
|
||||
// ---------- 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
|
||||
// collision, jamais dupliquée dans les templates.
|
||||
// ---------- Deux modales distinctes ----------
|
||||
// 1. #questFieldsModal (taille normale) : "je crée" — titre/objectif/
|
||||
// récompense/statut/résultat, + un bouton qui ouvre...
|
||||
// 2. #questDialogueModal (PLEIN ÉCRAN) : "j'ajoute les dialogues" — les
|
||||
// 3 colonnes de répliques, rien d'autre. Étapes séparées demandées
|
||||
// explicitement par l'utilisateur (pas un seul gros formulaire).
|
||||
// Créées une seule fois chacune (au premier appel), réutilisées ensuite —
|
||||
// partagées avec l'assistant "+ Action" de l'éditeur de collision,
|
||||
// jamais dupliquées dans les templates.
|
||||
var _questModalOnUse = null;
|
||||
var _questModalCurrentId = null;
|
||||
|
||||
function ensureQuestDialogueModal() {
|
||||
var modal = document.getElementById('questDialogueModal');
|
||||
function ensureQuestFieldsModal() {
|
||||
var modal = document.getElementById('questFieldsModal');
|
||||
if (modal) return modal;
|
||||
modal = document.createElement('div');
|
||||
modal.className = 'modal';
|
||||
modal.id = 'questDialogueModal';
|
||||
modal.id = 'questFieldsModal';
|
||||
modal.innerHTML =
|
||||
'<div class="modal-background" onclick="closeQuestDialogueModal()"></div>' +
|
||||
'<div class="modal-content questDialogueModalContent"><div class="box">' +
|
||||
'<div id="questDialogueModalBody"></div>' +
|
||||
'<div class="modal-background" onclick="closeQuestFieldsModal()"></div>' +
|
||||
'<div class="modal-content questFieldsModalContent"><div class="box">' +
|
||||
'<div id="questFieldsModalBody"></div>' +
|
||||
'</div></div>';
|
||||
document.body.appendChild(modal);
|
||||
return modal;
|
||||
@@ -105,25 +113,32 @@ function ensureQuestDialogueModal() {
|
||||
|
||||
// `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" épinglé en haut de la modale.
|
||||
function openQuestDialogueModal(questId, onUse) {
|
||||
// bouton "✓ Utiliser cette quête" dans les deux modales.
|
||||
function openQuestFieldsModal(questId, onUse) {
|
||||
_questModalOnUse = onUse || null;
|
||||
var modal = ensureQuestDialogueModal();
|
||||
_questModalCurrentId = questId;
|
||||
var modal = ensureQuestFieldsModal();
|
||||
modal.classList.add('is-active');
|
||||
renderQuestDialogueModalBody(questId);
|
||||
renderQuestFieldsModalBody(questId);
|
||||
}
|
||||
|
||||
function closeQuestDialogueModal() {
|
||||
var modal = document.getElementById('questDialogueModal');
|
||||
// _questModalOnUse/_questModalCurrentId ne sont PAS remis à null ici :
|
||||
// openQuestDialogueModal() ferme cette modale en s'ouvrant (voir plus
|
||||
// bas) et a besoin de les garder pour son propre bouton "✓ Utiliser
|
||||
// cette quête" — chaque point d'entrée (openQuestFieldsModal) les fixe
|
||||
// de toute façon fraîchement à chaque ouverture.
|
||||
function closeQuestFieldsModal() {
|
||||
var modal = document.getElementById('questFieldsModal');
|
||||
if (modal) modal.classList.remove('is-active');
|
||||
_questModalOnUse = null;
|
||||
_questModalCurrentId = null;
|
||||
}
|
||||
|
||||
function questUseThisQuest(questId) {
|
||||
var quest = questById(questId);
|
||||
if (_questModalOnUse && quest) _questModalOnUse(quest);
|
||||
closeQuestDialogueModal();
|
||||
closeQuestFieldsModal();
|
||||
_questModalOnUse = null;
|
||||
_questModalCurrentId = null;
|
||||
}
|
||||
|
||||
function questFieldRowHtml(quest) {
|
||||
@@ -144,6 +159,25 @@ function questFieldRowHtml(quest) {
|
||||
);
|
||||
}
|
||||
|
||||
function renderQuestFieldsModalBody(questId) {
|
||||
var quest = questById(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>'
|
||||
: '';
|
||||
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>' +
|
||||
'<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>' +
|
||||
useBtn +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
function questDialogueBubbleHtml(line, status, index) {
|
||||
return (
|
||||
'<div class="questBubbleRow questBubbleRow-' + line.speaker + '">' +
|
||||
@@ -172,7 +206,42 @@ function questDialogueColumnHtml(quest, status) {
|
||||
);
|
||||
}
|
||||
|
||||
var _questModalCurrentId = null;
|
||||
// Modale de dialogue — PLEIN ÉCRAN (voir .questDialogueModal en CSS),
|
||||
// ouverte DEPUIS la modale de fiche (bouton "💬 Ajouter les dialogues"),
|
||||
// jamais les deux à la fois : referme la fiche en s'ouvrant plutôt que de
|
||||
// les empiler, "← Fiche de la quête" y ramène.
|
||||
function ensureQuestDialogueModal() {
|
||||
var modal = document.getElementById('questDialogueModal');
|
||||
if (modal) return modal;
|
||||
modal = document.createElement('div');
|
||||
modal.className = 'modal questDialogueModal';
|
||||
modal.id = 'questDialogueModal';
|
||||
modal.innerHTML =
|
||||
'<div class="modal-background" onclick="closeQuestDialogueModal()"></div>' +
|
||||
'<div class="modal-content questDialogueModalContent"><div class="box">' +
|
||||
'<div id="questDialogueModalBody"></div>' +
|
||||
'</div></div>';
|
||||
document.body.appendChild(modal);
|
||||
return modal;
|
||||
}
|
||||
|
||||
function openQuestDialogueModal(questId) {
|
||||
_questModalCurrentId = questId;
|
||||
closeQuestFieldsModal();
|
||||
var modal = ensureQuestDialogueModal();
|
||||
modal.classList.add('is-active');
|
||||
renderQuestDialogueModalBody(questId);
|
||||
}
|
||||
|
||||
function closeQuestDialogueModal() {
|
||||
var modal = document.getElementById('questDialogueModal');
|
||||
if (modal) modal.classList.remove('is-active');
|
||||
}
|
||||
|
||||
function questBackToFieldsModal(questId) {
|
||||
closeQuestDialogueModal();
|
||||
openQuestFieldsModal(questId, _questModalOnUse);
|
||||
}
|
||||
|
||||
function renderQuestDialogueModalBody(questId) {
|
||||
var quest = questById(questId);
|
||||
@@ -184,12 +253,14 @@ function renderQuestDialogueModalBody(questId) {
|
||||
: '';
|
||||
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>' +
|
||||
'<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>' +
|
||||
'</div>' +
|
||||
'<div style="display:flex; gap:8px; align-items:center;">' + useBtn +
|
||||
'<button type="button" class="button is-small" onclick="closeQuestDialogueModal()">✕</button>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
questFieldRowHtml(quest) +
|
||||
'<p class="hint mt-2 mb-2">Dialogue joué selon l\'état de la quête au moment où le joueur parle au PNJ :</p>' +
|
||||
'<div class="questDialogueColumns">' + QUEST_STATUS_ORDER.map(function (s) { return questDialogueColumnHtml(quest, s); }).join('') + '</div>';
|
||||
}
|
||||
@@ -207,9 +278,7 @@ function questUpdateField(questId, field, value) {
|
||||
if (!json.quest) return;
|
||||
Object.assign(quest, json.quest);
|
||||
renderQuestListPage();
|
||||
if (document.getElementById('questDialogueModal') && document.getElementById('questDialogueModal').classList.contains('is-active')) {
|
||||
renderQuestDialogueModalBody(questId);
|
||||
}
|
||||
renderQuestFieldsModalBody(questId); // seule modale à porter des champs éditables
|
||||
});
|
||||
}
|
||||
|
||||
@@ -263,14 +332,13 @@ function _currentQuestModalId() {
|
||||
}
|
||||
|
||||
// ---------- Sélecteur de quête (assistant "+ Action" de l'éditeur de collision) ----------
|
||||
// Récupère la liste à jour (jamais présente d'avance dans scene_edit.html,
|
||||
// contrairement à quests.html) puis affiche des cartes + "+ Nouvelle
|
||||
// quête" DANS la modale de collision déjà ouverte (voir
|
||||
// renderCollisionWizardActionStep) ; choisir/créer une quête ouvre
|
||||
// ENSUITE la modale de dialogue (celle-ci empilée par-dessus), avec un
|
||||
// bouton "✓ Utiliser cette quête" qui referme les deux et finalise la
|
||||
// règle — exactement le comportement demandé : cliquer "Déclencher une
|
||||
// quête" ouvre l'éditeur de quête, pas un simple champ texte.
|
||||
// Affiche des cartes + "+ Nouvelle quête" DANS la modale de collision
|
||||
// déjà ouverte (voir renderCollisionWizardActionStep) ; choisir/créer une
|
||||
// quête ouvre ENSUITE la modale de FICHE (celle-ci par-dessus), avec un
|
||||
// bouton "✓ Utiliser cette quête" (disponible aussi depuis la modale de
|
||||
// dialogue) qui referme tout et finalise la règle — exactement le
|
||||
// comportement demandé : cliquer "Déclencher une quête" ouvre l'éditeur
|
||||
// de quête, pas un simple champ texte.
|
||||
function questPickerHtml(quests) {
|
||||
if (!quests.length) {
|
||||
return '<p class="hint mb-2">Aucune quête pour l\'instant.</p>';
|
||||
@@ -299,7 +367,7 @@ function renderQuestPickerStep(onUse) {
|
||||
}
|
||||
|
||||
function questPickerChoose(questId) {
|
||||
openQuestDialogueModal(questId, _questPickerOnUse);
|
||||
openQuestFieldsModal(questId, _questPickerOnUse);
|
||||
}
|
||||
|
||||
function questPickerCreate() {
|
||||
@@ -309,7 +377,7 @@ function questPickerCreate() {
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (json) {
|
||||
QUESTS.unshift(json.quest);
|
||||
openQuestDialogueModal(json.quest.id, _questPickerOnUse);
|
||||
openQuestFieldsModal(json.quest.id, _questPickerOnUse);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user