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:
@@ -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