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:
@@ -9,15 +9,15 @@
|
||||
// elle est validée une seconde fois avant d'être enregistrée).
|
||||
|
||||
var COLLISION_ACTION_LABELS = {
|
||||
quete: '🗺️ Déclencher une quête', attaque: '⚔️ Attaquer la cible',
|
||||
evenement: '📣 Déclencher un événement', interagir: '🔑 Interagir avec une touche',
|
||||
quete: 'Déclencher une quête', attaque: 'Attaquer la cible',
|
||||
evenement: 'Déclencher un événement', interagir: 'Interagir avec une touche',
|
||||
};
|
||||
// "attaque"/"evenement" retirés de l'assistant (pas besoin pour le
|
||||
// moment, demande explicite) — server-side (screens/rendering/
|
||||
// collision_rules.py::ACTION_TYPES) les accepte encore telles quelles,
|
||||
// pour ne jamais casser une règle déjà enregistrée avec l'un des deux.
|
||||
var COLLISION_WIZARD_ACTION_TYPES = ['quete', 'interagir'];
|
||||
var COLLISION_TRIGGER_LABELS = { collision: '💥 À la collision', perimetre: '📍 Dans un périmètre' };
|
||||
var COLLISION_TRIGGER_LABELS = { collision: 'À la collision', perimetre: 'Dans un périmètre' };
|
||||
|
||||
function collisionRuleObjectLabel(obj) {
|
||||
var kindLabel = obj.kind === 'personnage' ? (obj.role === 'ennemie' ? 'Ennemi' : 'PNJ') : 'Décor';
|
||||
@@ -52,15 +52,26 @@ function collisionRuleActionSummary(action) {
|
||||
function collisionRuleChainHtml(rule, ruleIndex, objectId) {
|
||||
var links = [
|
||||
{ kind: 'trigger', text: collisionRuleTriggerSummary(rule) },
|
||||
{ kind: rule.action.type, text: collisionRuleActionSummary(rule.action) },
|
||||
{ kind: rule.action.type, text: collisionRuleActionSummary(rule.action), questId: rule.action.quete_id },
|
||||
];
|
||||
var action = rule.action;
|
||||
while (action.type === 'interagir' && action.sub_action) {
|
||||
links.push({ kind: action.sub_action.type, text: collisionRuleActionSummary(action.sub_action) });
|
||||
links.push({ kind: action.sub_action.type, text: collisionRuleActionSummary(action.sub_action), questId: action.sub_action.quete_id });
|
||||
action = action.sub_action;
|
||||
}
|
||||
// Un maillon "quete" (kind==='quete') ouvre directement sa modale de
|
||||
// dialogue (voir openQuestDialogueModal(), static/js/quests/
|
||||
// quest-editor.js) au clic — demande explicite, plutôt qu'un simple
|
||||
// texte informatif comme les autres maillons. Reste un <span> comme les
|
||||
// autres (jamais un <button>, dont le style natif — apparence système,
|
||||
// marges par défaut — grignotait la pointe en clip-path malgré les
|
||||
// resets CSS) : un onclick sur un <span> suffit à le rendre cliquable,
|
||||
// ".collisionRuleLink[data-kind='quete']" ajoute juste le curseur/hover
|
||||
// qui le distingue visuellement (voir static/style.css).
|
||||
var linksHtml = links.map(function (link, i) {
|
||||
return '<span class="collisionRuleLink" data-kind="' + link.kind + '" style="animation-delay:' + (i * 60) + 'ms">' +
|
||||
var clickable = link.kind === 'quete' && link.questId != null;
|
||||
var onclick = clickable ? ' onclick="openQuestDialogueModal(' + link.questId + ')"' : '';
|
||||
return '<span class="collisionRuleLink" data-kind="' + link.kind + '" style="animation-delay:' + (i * 60) + 'ms"' + onclick + '>' +
|
||||
link.text.replace(/</g, '<') + '</span>';
|
||||
}).join('');
|
||||
return (
|
||||
@@ -72,6 +83,21 @@ function collisionRuleChainHtml(rule, ruleIndex, objectId) {
|
||||
);
|
||||
}
|
||||
|
||||
// obj.thumbnail_html (screens.render_scene_object) porte un style inline
|
||||
// PENSÉ POUR LE CANEVAS (position:absolute + left/top en dur, voir
|
||||
// screens/scenes/render_scene_object.py) — un personnage déplacé loin
|
||||
// dans la scène (left/top de plusieurs centaines de px) ressortait donc
|
||||
// hors du petit cadre 120×120 de cette miniature : la règle CSS
|
||||
// `.collisionRuleCardThumb img` (voir static/style.css) est censée le
|
||||
// neutraliser (position:static !important etc.), mais s'appuyer sur un
|
||||
// forçage CSS pour annuler un style inline entier reste fragile. Plus
|
||||
// robuste : reconstruire ici une balise <img> "propre", sans AUCUN style
|
||||
// hérité du canevas — seul le src (l'image elle-même) est repris.
|
||||
function collisionRuleThumbHtml(obj) {
|
||||
var match = /\ssrc="([^"]*)"/.exec(obj.thumbnail_html || '');
|
||||
return match ? '<img src="' + match[1] + '" alt="">' : '';
|
||||
}
|
||||
|
||||
function collisionRuleCardHtml(obj) {
|
||||
var rulesHtml = obj.rules.length
|
||||
? obj.rules.map(function (rule, i) { return collisionRuleChainHtml(rule, i, obj.id); }).join('')
|
||||
@@ -79,11 +105,11 @@ function collisionRuleCardHtml(obj) {
|
||||
return (
|
||||
'<div class="collisionRuleCard" id="collisionRuleCard-' + obj.id + '">' +
|
||||
'<div class="collisionRuleCardHeader">' +
|
||||
'<div class="collisionRuleCardThumb">' + obj.thumbnail_html + '</div>' +
|
||||
'<div class="collisionRuleCardThumb">' + collisionRuleThumbHtml(obj) + '</div>' +
|
||||
'<h3 class="collisionRuleCardTitle">' + collisionRuleObjectLabel(obj).replace(/</g, '<') + '</h3>' +
|
||||
'</div>' +
|
||||
'<div class="collisionRuleCardRules">' + rulesHtml + '</div>' +
|
||||
'<button type="button" class="collisionRuleAddBtn" onclick="openCollisionRuleModal(' + obj.id + ')">✚ Action</button>' +
|
||||
'<button type="button" class="collisionRuleAddBtn" onclick="openCollisionRuleModal(' + obj.id + ')">Action</button>' +
|
||||
'</div>'
|
||||
);
|
||||
}
|
||||
@@ -154,24 +180,15 @@ function collisionWizardBodyEl() {
|
||||
|
||||
// Étape 1 — déclencheur : collision directe, ou périmètre (px réglable,
|
||||
// valeur par défaut COLLISION_DEFAULT_PERIMETRE_PX).
|
||||
// Sépare "emoji" et "libellé" d'un texte "🗺️ Déclencher une quête" pour
|
||||
// les afficher sur deux lignes dans une carte-choix (voir .collisionWizardChoice).
|
||||
function collisionSplitEmojiLabel(text) {
|
||||
var m = text.match(/^(\S+)\s+(.*)$/);
|
||||
return m ? { emoji: m[1], label: m[2] } : { emoji: '', label: text };
|
||||
}
|
||||
|
||||
function renderCollisionWizardTriggerStep() {
|
||||
var collision = collisionSplitEmojiLabel(COLLISION_TRIGGER_LABELS.collision);
|
||||
var perimetre = collisionSplitEmojiLabel(COLLISION_TRIGGER_LABELS.perimetre);
|
||||
collisionWizardBodyEl().innerHTML =
|
||||
'<p class="hint mb-2">Cette règle se déclenche...</p>' +
|
||||
'<div class="collisionWizardChoices">' +
|
||||
'<button type="button" class="collisionWizardChoice" style="animation-delay:0ms" onclick="collisionWizardChooseTrigger(\'collision\')">' +
|
||||
'<span class="emoji">' + collision.emoji + '</span><span>' + collision.label + '</span>' +
|
||||
'<span>' + COLLISION_TRIGGER_LABELS.collision + '</span>' +
|
||||
'</button>' +
|
||||
'<button type="button" class="collisionWizardChoice" style="animation-delay:40ms" onclick="collisionWizardChooseTrigger(\'perimetre\')">' +
|
||||
'<span class="emoji">' + perimetre.emoji + '</span><span>' + perimetre.label + '</span>' +
|
||||
'<span>' + COLLISION_TRIGGER_LABELS.perimetre + '</span>' +
|
||||
'<span class="fieldRow" style="align-items:center; gap:6px;" onclick="event.stopPropagation()">' +
|
||||
'<input type="number" min="1" id="collisionWizardPerimetrePx" value="' + COLLISION_DEFAULT_PERIMETRE_PX + '" style="width:64px;" class="input is-small">' +
|
||||
'<span class="hint">px</span>' +
|
||||
@@ -196,9 +213,8 @@ function collisionWizardChooseTrigger(trigger) {
|
||||
function renderCollisionWizardActionStep(nested) {
|
||||
var types = nested ? COLLISION_WIZARD_ACTION_TYPES.filter(function (t) { return t !== 'interagir'; }) : COLLISION_WIZARD_ACTION_TYPES;
|
||||
var buttons = types.map(function (type, i) {
|
||||
var split = collisionSplitEmojiLabel(COLLISION_ACTION_LABELS[type]);
|
||||
return '<button type="button" class="collisionWizardChoice" style="animation-delay:' + (i * 40) + 'ms" onclick="collisionWizardChooseAction(\'' + type + '\', ' + (nested ? 'true' : 'false') + ')">' +
|
||||
'<span class="emoji">' + split.emoji + '</span><span>' + split.label + '</span>' +
|
||||
'<span>' + COLLISION_ACTION_LABELS[type] + '</span>' +
|
||||
'</button>';
|
||||
}).join('');
|
||||
collisionWizardBodyEl().innerHTML =
|
||||
@@ -253,18 +269,25 @@ function collisionWizardChooseAction(type, nested) {
|
||||
if (type === 'evenement') {
|
||||
var eventIds = Object.keys(CUSTOM_EVENTS_MAP);
|
||||
if (!eventIds.length) {
|
||||
collisionWizardBodyEl().innerHTML = '<p class="hint">Aucun événement personnalisé pour l\'instant — crée-en un dans l\'onglet "📣 Événements", puis reviens ici.</p>';
|
||||
collisionWizardBodyEl().innerHTML = '<p class="hint">Aucun événement personnalisé pour l\'instant — crée-en un dans l\'onglet "Événements", puis reviens ici.</p>';
|
||||
return;
|
||||
}
|
||||
var buttons = eventIds.map(function (id, i) {
|
||||
return '<button type="button" class="collisionWizardChoice" style="animation-delay:' + (i * 40) + 'ms" onclick="collisionWizardFinalizeAction({type:\'evenement\', evenement_id:' + id + '}, ' + (nested ? 'true' : 'false') + ')">' +
|
||||
'<span class="emoji">📣</span><span>' + CUSTOM_EVENTS_MAP[id].name.replace(/</g, '<') + '</span>' +
|
||||
'<span>' + CUSTOM_EVENTS_MAP[id].name.replace(/</g, '<') + '</span>' +
|
||||
'</button>';
|
||||
}).join('');
|
||||
collisionWizardBodyEl().innerHTML = '<p class="hint mb-2">Quel événement ?</p><div class="collisionWizardChoices">' + buttons + '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
if (typeof COLLISION_RULE_OBJECTS !== 'undefined') renderCollisionRuleCards();
|
||||
});
|
||||
// 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 "🧩 Collision" 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 COLLISION_RULE_OBJECTS !== 'undefined') renderCollisionRuleCards();
|
||||
|
||||
@@ -8,16 +8,97 @@
|
||||
// animation-timeline.js, personnage-panel.js, personnage-preview.js) est
|
||||
// réutilisée TELLE QUELLE, chargée telle quelle par scene_edit.html.
|
||||
|
||||
// AJAX (voir routes/scenes/scene_object_add.py — répond en JSON dès que
|
||||
// "X-Requested-With: fetch" est posé) : pose l'objet SANS recharger la
|
||||
// page, pour ne plus faire "disparaître" un instant l'éditeur de collision/
|
||||
// quêtes (état JS entier réinitialisé par un rechargement complet) ni
|
||||
// perdre l'onglet/le scroll en cours. Le nouvel objet n'est PAS
|
||||
// automatiquement sélectionné/ouvert dans le panneau de propriétés (voir
|
||||
// onSceneObjectMouseDown, qui recharge encore la page pour ça) — juste
|
||||
// posé sur le canevas, cliquable ensuite comme n'importe quel autre.
|
||||
function addSceneObject(kind, forgeCharacter, backgroundSlug) {
|
||||
var form = document.createElement('form');
|
||||
form.method = 'post';
|
||||
form.action = "/game/" + GAME_SLUG + "/screens/" + SCREEN_ID + "/scene-objects/add";
|
||||
var html = '<input type="hidden" name="kind" value="' + kind + '">';
|
||||
if (forgeCharacter) html += '<input type="hidden" name="forge_character" value="' + forgeCharacter + '">';
|
||||
if (backgroundSlug) html += '<input type="hidden" name="background_slug" value="' + backgroundSlug + '">';
|
||||
form.innerHTML = html;
|
||||
document.body.appendChild(form);
|
||||
form.requestSubmit();
|
||||
var body = new FormData();
|
||||
body.append('kind', kind);
|
||||
if (forgeCharacter) body.append('forge_character', forgeCharacter);
|
||||
if (backgroundSlug) body.append('background_slug', backgroundSlug);
|
||||
fetch("/game/" + GAME_SLUG + "/screens/" + SCREEN_ID + "/scene-objects/add", {
|
||||
method: 'POST', headers: { 'X-Requested-With': 'fetch' }, body: body,
|
||||
})
|
||||
.then(function (r) {
|
||||
// Secours si le serveur répond encore par une redirection HTML
|
||||
// classique (ex. déploiement pas encore à jour côté serveur alors
|
||||
// que ce script, lui, l'est déjà) : l'objet est de toute façon déjà
|
||||
// posé en base à ce stade (voir screens/scenes/add_scene_object.py),
|
||||
// seul un rechargement peut alors l'afficher.
|
||||
var contentType = r.headers.get('content-type') || '';
|
||||
if (!r.ok || contentType.indexOf('application/json') === -1) {
|
||||
window.location.reload();
|
||||
return null;
|
||||
}
|
||||
return r.json();
|
||||
})
|
||||
.then(function (json) {
|
||||
if (!json) return;
|
||||
var canvas = document.getElementById('canvas');
|
||||
var el = document.createElement('div');
|
||||
el.className = 'canvasElement';
|
||||
el.id = 'el-' + json.id;
|
||||
el.style.position = 'absolute';
|
||||
el.style.left = json.x + 'px'; el.style.top = json.y + 'px';
|
||||
el.style.width = json.width + 'px'; el.style.height = json.height + 'px';
|
||||
el.setAttribute('onmousedown', 'onSceneObjectMouseDown(event, ' + json.id + ')');
|
||||
el.innerHTML =
|
||||
'<div class="canvasElementInner">' + json.rendered_html + '</div>' +
|
||||
'<div class="resizeHandle" onmousedown="onSceneObjectResizeMouseDown(event, ' + json.id + ')"></div>';
|
||||
canvas.appendChild(el);
|
||||
refreshCollisionRuleObjects();
|
||||
if (json.animations) {
|
||||
ELEMENT_ANIMATIONS_MAP[json.id] = json.animations;
|
||||
restartPersonnageIdlePreview(json.id, json.animations);
|
||||
}
|
||||
})
|
||||
// Filet de sécurité : mieux vaut un rechargement (l'objet est déjà en
|
||||
// base à ce stade) qu'un bouton "+ Ajouter" qui semble ne plus rien
|
||||
// faire du tout si jamais une erreur imprévue survient ici.
|
||||
.catch(function () { window.location.reload(); });
|
||||
}
|
||||
|
||||
// AJAX (voir routes/scenes/scene_object_delete.py) : bouton 🗑️ du panneau
|
||||
// de propriétés (toujours l'objet SÉLECTIONNÉ, seul cas où ce bouton est
|
||||
// affiché) — supprime l'objet SANS recharger la page ni même redemander
|
||||
// #builder3 au serveur (déjà retiré du DOM ici) : ne plus faire "vider"
|
||||
// l'éditeur de collision/quêtes le temps d'un remplacement de <main>,
|
||||
// comme le faisait la soumission de formulaire classique interceptée par
|
||||
// pjax.js (voir son commentaire d'en-tête).
|
||||
function deleteSceneObject(id) {
|
||||
fetch('/game/' + GAME_SLUG + '/scene-objects/' + id + '/delete', {
|
||||
method: 'POST', headers: { 'X-Requested-With': 'fetch' },
|
||||
})
|
||||
.then(function (r) {
|
||||
var contentType = r.headers.get('content-type') || '';
|
||||
if (!r.ok || contentType.indexOf('application/json') === -1) {
|
||||
window.location.reload();
|
||||
return null;
|
||||
}
|
||||
return r.json();
|
||||
})
|
||||
.then(function (json) {
|
||||
if (!json || !json.ok) return;
|
||||
var el = document.getElementById('el-' + id);
|
||||
if (el) el.remove();
|
||||
if (typeof _stopPersonnageIdlePreview === 'function') _stopPersonnageIdlePreview(id);
|
||||
refreshCollisionRuleObjects();
|
||||
// L'objet supprimé était forcément le sélectionné (seul cas où 🗑️
|
||||
// est affiché) — referme le panneau de propriétés et l'URL, sans
|
||||
// requête supplémentaire au serveur.
|
||||
var propsBody = document.querySelector('#propsPanelFloat .floatPanelBody');
|
||||
if (propsBody) {
|
||||
propsBody.innerHTML = '<div class="hint" style="padding:20px 0;">Sélectionne un objet (sur la scène ou dans la liste) pour voir et modifier ses propriétés ici.</div>';
|
||||
}
|
||||
CURRENT_SELECTED_ID = null;
|
||||
history.replaceState(null, '', SCREEN_EDIT_URL);
|
||||
})
|
||||
.catch(function () { window.location.reload(); });
|
||||
}
|
||||
|
||||
// Taille de la caméra (voir "🎮 Jeu 2D" en haut de templates/
|
||||
@@ -152,7 +233,17 @@ function onSceneObjectMouseDown(e, id) {
|
||||
updateScenePosFields(curLeft, curTop, width, height);
|
||||
saveSceneObjectGeometry(id, curLeft, curTop, width, height);
|
||||
} else {
|
||||
window.location.href = SCREEN_EDIT_URL + '?selected=' + id;
|
||||
// goToSelected() (static/js/screen_edit/tree-panels.js, déjà utilisée
|
||||
// par l'éditeur document) : va chercher #builder3 en arrière-plan et
|
||||
// le remplace sur place plutôt que de recharger toute la page —
|
||||
// demande explicite ("à chaque fois que je clique sur un élément la
|
||||
// page se recharge"). openPropsPanel() : si le panneau était fermé
|
||||
// (état mémorisé dans localStorage, voir restoreFloatPanelState()),
|
||||
// cliquer un objet doit le rouvrir pour montrer ses propriétés —
|
||||
// sinon initBuilderPanel() (rappelé par goToSelected) le laissait
|
||||
// fermé, panneau à jour mais invisible.
|
||||
openPropsPanel();
|
||||
goToSelected(id);
|
||||
}
|
||||
}
|
||||
document.addEventListener('mousemove', onMove);
|
||||
@@ -208,7 +299,7 @@ function onSceneObjectResizeMouseDown(e, id) {
|
||||
// Champs numériques Position & taille du panneau de propriétés — mirror
|
||||
// du comportement équivalent pour l'éditeur document (voir l'IIFE dans
|
||||
// initBuilderPanel(), panel-init.js), mais ids/route distincts (pixels).
|
||||
(function () {
|
||||
function initScenePosFields() {
|
||||
var xEl = document.getElementById('scene-pos-x'), yEl = document.getElementById('scene-pos-y');
|
||||
var wEl = document.getElementById('scene-pos-w'), hEl = document.getElementById('scene-pos-h');
|
||||
if (!xEl || !wEl || !CURRENT_SELECTED_ID) return;
|
||||
@@ -223,7 +314,8 @@ function onSceneObjectResizeMouseDown(e, id) {
|
||||
saveSceneObjectGeometry(CURRENT_SELECTED_ID, x, y, w, h);
|
||||
}
|
||||
[xEl, yEl, wEl, hEl].forEach(function (el) { el.addEventListener('change', apply); });
|
||||
})();
|
||||
}
|
||||
initScenePosFields();
|
||||
|
||||
// Personnage d'un objet de scène : mirror de swapPersonnageCharacter()
|
||||
// (personnage-panel.js), mais POST vers /scene-objects/.../personnage
|
||||
@@ -263,7 +355,25 @@ function saveSceneObjectRole(role) {
|
||||
fetch('/game/' + GAME_SLUG + '/scene-objects/' + objectId + '/role', {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ role: role }),
|
||||
});
|
||||
}).then(refreshCollisionRuleObjects);
|
||||
}
|
||||
|
||||
// Réutilisé après ajout/suppression/changement de rôle ou de nom (voir
|
||||
// routes/scenes/scene_collision_rule_objects.py) : un changement de rôle
|
||||
// peut faire apparaître/disparaître une carte (passage à/depuis "joueur",
|
||||
// jamais dans "🧩 Collision") et un changement de nom change son libellé
|
||||
// affiché — recalculée entièrement côté serveur plutôt que rejouée en JS
|
||||
// (même logique déjà nécessaire pour le rôle implicite d'une scène posée
|
||||
// avant ce réglage, voir build_collision_rule_objects) SANS recharger la
|
||||
// page.
|
||||
function refreshCollisionRuleObjects() {
|
||||
if (typeof COLLISION_RULE_OBJECTS === 'undefined') return;
|
||||
fetch('/game/' + GAME_SLUG + '/screens/' + SCREEN_ID + '/collision-rule-objects')
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (json) {
|
||||
COLLISION_RULE_OBJECTS = json;
|
||||
renderCollisionRuleCards();
|
||||
});
|
||||
}
|
||||
|
||||
// Boîte de collision (voir screens/rendering/collision_settings.py,
|
||||
@@ -475,9 +585,27 @@ function saveSceneObjectName(objectId, name) {
|
||||
fetch('/game/' + GAME_SLUG + '/scene-objects/' + objectId + '/name', {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name: name }),
|
||||
}).then(function () {
|
||||
refreshCollisionRuleObjects();
|
||||
refreshSceneObjectNames();
|
||||
});
|
||||
}
|
||||
|
||||
// SCENE_OBJECT_NAMES (voir routes/scenes/scene_object_names_list.py) :
|
||||
// posée UNE SEULE FOIS au chargement complet de la page (<script> inline
|
||||
// en bas, HORS de #builder3, jamais rafraîchie par une simple sélection
|
||||
// sans rechargement, voir goToSelected()) — sans ce rafraîchissement,
|
||||
// renommer un personnage puis ouvrir la modale de dialogue dans la MÊME
|
||||
// session ne faisait jamais apparaître son nouveau nom dans "qui parle"
|
||||
// (voir questSpeakerOptions(), static/js/quests/quest-editor.js) tant
|
||||
// qu'aucun vrai rechargement de page n'avait eu lieu entre temps.
|
||||
function refreshSceneObjectNames() {
|
||||
if (typeof SCENE_OBJECT_NAMES === 'undefined') return;
|
||||
fetch('/game/' + GAME_SLUG + '/scene-object-names')
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (json) { SCENE_OBJECT_NAMES = json; });
|
||||
}
|
||||
|
||||
// "🎨 Style" du widget "💬 Boîte de dialogue" (voir screens/rendering/
|
||||
// dialogue_box_style.py) — envoie TOUJOURS les 7 champs ensemble (même
|
||||
// convention "replace-all" que saveSceneObjectCollision), jamais un seul
|
||||
@@ -509,7 +637,7 @@ function saveDialogueBoxStyle(objectId) {
|
||||
});
|
||||
}
|
||||
|
||||
(function () {
|
||||
function initPersonnageCommandsPanel() {
|
||||
var body = document.getElementById('personnageCommandsBody');
|
||||
if (!body) return;
|
||||
var objectId = parseInt(body.dataset.elementId, 10);
|
||||
@@ -521,7 +649,47 @@ function saveDialogueBoxStyle(objectId) {
|
||||
if (el) el.addEventListener('change', saveSceneObjectCommands);
|
||||
});
|
||||
var container = document.getElementById('personnageCommandsExtra');
|
||||
(window.PERSONNAGE_COMMANDS_EXTRA || []).forEach(function (e) {
|
||||
// Lu depuis data-commands-extra (posé sur CET élément par le serveur,
|
||||
// voir templates/scene_edit.html) plutôt que la variable globale
|
||||
// PERSONNAGE_COMMANDS_EXTRA (déclarée dans le <script> inline en bas de
|
||||
// page, HORS de #builder3) : cette fonction est aussi rappelée après un
|
||||
// changement de sélection SANS rechargement (voir initSceneBuilderPanel
|
||||
// ci-dessous), qui ne remplace que #builder3 — cette variable globale
|
||||
// resterait alors bloquée sur les commandes du PRÉCÉDENT objet
|
||||
// sélectionné.
|
||||
var extra = [];
|
||||
try { extra = JSON.parse(body.dataset.commandsExtra || '[]'); } catch (e) { extra = []; }
|
||||
extra.forEach(function (e) {
|
||||
container.appendChild(personnageCommandExtraRowEl(objectId, e.touche, e.animation));
|
||||
});
|
||||
})();
|
||||
}
|
||||
initPersonnageCommandsPanel();
|
||||
|
||||
// Rappelée après chaque changement de sélection d'objet de scène SANS
|
||||
// rechargement de page (voir onSceneObjectMouseDown -> goToSelected(),
|
||||
// static/js/screen_edit/tree-panels.js::refreshBuilderPanel, qui l'appelle
|
||||
// juste après avoir remplacé #builder3 et initBuilderPanel()) — reproduit
|
||||
// ici ce que ce script fait déjà tout seul au chargement initial de la
|
||||
// page (voir initScenePosFields()/initPersonnageCommandsPanel() ci-dessus)
|
||||
// + relance l'aperçu animé de tous les personnages du canevas (leurs <img>
|
||||
// viennent d'être recréés par le même remplacement, voir
|
||||
// startAllPersonnagePreviews()/personnage-preview.js).
|
||||
//
|
||||
// .builderCanvasArea (les onglets "🖥️ Scène"/"🧩 Collision"/"🗺️ Quêtes"/
|
||||
// "📣 Événements") est ENTIÈREMENT à l'intérieur de #builder3 (voir
|
||||
// templates/scene_edit.html) — le remplacer réinjecte donc aussi
|
||||
// #collisionRulesCards/le conteneur des quêtes tels que le SERVEUR les
|
||||
// rend, c'est-à-dire VIDES (ces listes sont rendues entièrement en JS,
|
||||
// jamais en Jinja, voir collision-rules-editor.js/quest-editor.js) : sans
|
||||
// les redessiner ici, les onglets "🧩 Collision"/"🗺️ Quêtes" semblaient se
|
||||
// "vider" à chaque sélection d'un objet de scène. COLLISION_RULE_OBJECTS/
|
||||
// QUESTS_JSON eux-mêmes ne sont PAS affectés par ce remplacement (déclarés
|
||||
// dans le <script> inline en bas de page, HORS de #builder3) — il suffit
|
||||
// donc de rejouer leur rendu, pas de les recharger.
|
||||
function initSceneBuilderPanel() {
|
||||
initScenePosFields();
|
||||
initPersonnageCommandsPanel();
|
||||
if (typeof startAllPersonnagePreviews === 'function') startAllPersonnagePreviews();
|
||||
if (typeof renderCollisionRuleCards === 'function') renderCollisionRuleCards();
|
||||
if (typeof renderQuestListPage === 'function') renderQuestListPage();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user