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();
|
||||
|
||||
Reference in New Issue
Block a user