Simplifie les événements : notification pure, sans paramètre

Retour de l'utilisateur sur le premier jet : "Déclencher un événement"
ne doit JAMAIS faire choisir un élément — c'est une notification pure,
rien de plus. C'est à l'ÉCOUTEUR (déclencheur "Sur un événement
personnalisé" → condition → action) de décider quoi faire ensuite, avec
ses réglages habituels (cible fixe, "Ligne cliquée"...), jamais à
l'événement de transporter un paramètre.

Retire donc tout le mécanisme de transmission ajouté au tour précédent
(has_element_param, target_element_from_event, EVENT_ROW_ID,
window.lastEventParams) :

- db/custom_events/ : _custom_events perd sa colonne has_element_param —
  un événement n'est plus qu'un nom + une description.
- screens/flow/ : retire target_element_from_event (colonne ajoutée par
  ALTER TABLE, laissée inerte sur les bases déjà migrées — sans
  conséquence, plus jamais lue ni écrite) et la constante EVENT_ROW_ID.
- routes/flow/flow_node_run_data.py : retire la résolution EVENT_ROW_ID,
  revient à sa forme d'origine (seul CLICKED_ROW_ID reste géré).
- templates/screen_edit.html : le nœud Action "Déclencher un événement"
  n'a plus qu'un sélecteur d'événement — plus de champs élément/ligne.
  Le nœud Action "Modifier un élément" perd la case "Utiliser l'élément
  transmis par l'événement en cours". L'onglet Événements perd la case
  à cocher "Paramètre" (création et édition).
- templates/play.html : window.dispatchGameEvent(eventId) ne prend plus
  que l'id de l'événement — scan global inchangé, mais ne pose plus
  aucun window.lastEventParams. modifier_element et readFieldValue
  reviennent à leur résolution d'origine (plus de branche event-aware).

208 tests au total (2 tests retirés, devenus sans objet : la
persistance de target_element_from_event et la résolution serveur
d'EVENT_ROW_ID).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
william
2026-08-29 20:39:42 +02:00
co-authored by Claude Sonnet 5
parent 666aa892e0
commit 2c59e54556
13 changed files with 76 additions and 273 deletions
+19 -64
View File
@@ -788,46 +788,16 @@
bindHoverTexts();
bindHoverTriggers();
return Promise.resolve();
} else if (node.action_type === 'modifier_element' && (node.target_element_id || node.target_element_from_event)) {
// target_element_from_event (voir screens/custom_events/) : résout
// dynamiquement "l'élément transmis par l'événement en cours"
// (window.lastEventParams, posé par dispatchGameEvent() juste avant
// d'exécuter ce graphe) plutôt qu'une cible fixe choisie dans
// l'éditeur — contourne la contrainte de clé étrangère de
// target_element_id, qui empêche d'y stocker un sentinel comme
// EVENT_ROW_ID directement (voir screens/flow/ensure_flow_schema.py).
//
// data-element-id N'EST PAS unique quand l'élément vit dans un
// Répéteur : chaque ligne rejoue le MÊME modèle, donc le MÊME
// data-element-id se répète à l'identique sur chaque ligne (voir
// render_repeater.py, seul data-row-id — posé sur .repeaterItem,
// l'englobant — distingue réellement une ligne d'une autre). Un
// simple document.querySelector('[data-element-id]') global
// prendrait toujours la PREMIÈRE ligne, jamais la bonne — sans
// rapport avec l'événement reçu. Quand l'événement transmet aussi
// une ligne (row_id), la recherche est donc scopée à L'INTÉRIEUR de
// ce .repeaterItem précis ; sinon (élément fixe, ou événement sans
// paramètre de ligne), recherche globale comme avant.
const resolvedElementId = node.target_element_from_event
? (window.lastEventParams ? window.lastEventParams.element_id : null)
: node.target_element_id;
const resolvedRowId = node.target_element_from_event && window.lastEventParams
? window.lastEventParams.row_id : null;
let targetEl = null;
if (resolvedElementId != null) {
if (resolvedRowId != null) {
const rowEl = document.querySelector('.repeaterItem[data-row-id="' + resolvedRowId + '"]');
targetEl = rowEl ? rowEl.querySelector('[data-element-id="' + resolvedElementId + '"]') : null;
} else {
targetEl = document.querySelector('[data-element-id="' + resolvedElementId + '"]');
}
}
} else if (node.action_type === 'modifier_element' && node.target_element_id) {
const targetEl = document.querySelector('[data-element-id="' + node.target_element_id + '"]');
if (targetEl) applyElementProperty(targetEl, node.element_property, node.element_value);
return Promise.resolve();
} else if (node.action_type === 'declencher_evenement' && node.target_custom_event_id) {
let dispatchRowId = node.target_row_id;
if (dispatchRowId === -1) dispatchRowId = window.lastClickedRowId; // CLICKED_ROW_ID
window.dispatchGameEvent(node.target_custom_event_id, node.target_element_id || null, dispatchRowId != null ? dispatchRowId : null);
// Notification pure (voir screens/custom_events/) : ne fait que
// signaler l'événement, jamais choisir un élément — c'est à
// l'écouteur (déclencheur → condition → action) de décider quoi
// faire, avec ses propres réglages habituels.
window.dispatchGameEvent(node.target_custom_event_id);
return Promise.resolve();
} else if (node.action_type === 'activer_onglet' && node.target_element_id) {
// 2.3 — panneau à onglets / visibilité mutuellement exclusive : montre
@@ -864,16 +834,12 @@
return new Promise(function(resolve) { setTimeout(resolve, ms); });
} else if (node.action_type === 'modifier_donnee') {
const runDataUrl = "{{ url_for('flow_node_run_data', slug=game.slug, node_id=999999) }}".replace('999999', node.id);
// clicked_row_id/event_row_id : utilisés côté serveur SEULEMENT si ce
// nœud vise respectivement CLICKED_ROW_ID ou EVENT_ROW_ID (voir
// flow_node_run_data.py) — sans effet sinon, donc toujours transmis
// sans condition ici.
// clicked_row_id : utilisé côté serveur SEULEMENT si ce nœud vise la
// ligne cliquée (CLICKED_ROW_ID, voir flow_node_run_data.py) — sans
// effet sinon, donc toujours transmis sans condition ici.
return fetch(runDataUrl, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
clicked_row_id: window.lastClickedRowId,
event_row_id: window.lastEventParams ? window.lastEventParams.row_id : null,
})
body: JSON.stringify({ clicked_row_id: window.lastClickedRowId })
})
.then(r => r.json())
.then(() => refreshRuntimeData());
@@ -896,12 +862,6 @@
// résolue seulement au moment de l'évaluation via le dernier clic
// capturé (voir bindClicks() plus haut).
if (rowId === -1) rowId = window.lastClickedRowId;
// EVENT_ROW_ID (-2, voir screens/flow/constants.py) : "la ligne de
// Répéteur transmise par l'événement personnalisé en cours" — même
// principe que CLICKED_ROW_ID ci-dessus, résolue via
// window.lastEventParams (posé par dispatchGameEvent()) plutôt que le
// dernier clic capturé.
if (rowId === -2) rowId = window.lastEventParams ? window.lastEventParams.row_id : null;
const rows = gameData.data[String(definitionId)] || [];
const row = rows.find(r => r.id === rowId);
return row ? row[fieldName] : undefined;
@@ -1056,25 +1016,20 @@
}
// Déclenche un événement personnalisé (voir screens/custom_events/,
// action "declencher_evenement") : scan GLOBAL de gameData.flows, même
// principe que findTriggerNode()/runScreenShowTriggers() ci-dessus — un
// action "declencher_evenement") : une pure NOTIFICATION, sans aucun
// paramètre — scan GLOBAL de gameData.flows, même principe que
// findTriggerNode()/runScreenShowTriggers() ci-dessus, puisqu'un
// écouteur (nœud Déclencheur trigger_event="evenement") peut vivre sur
// N'IMPORTE QUELLE autre scène ou modèle, pas seulement l'écran affiché.
// elementId/rowId (potentiellement null si l'événement n'a pas de
// paramètre) sont posés sur window.lastEventParams AVANT de lancer
// chaque graphe trouvé, pour que les nœuds en aval (ex. "Modifier un
// élément" avec target_element_from_event, voir applyElementProperty
// plus haut/runActionNode plus bas) puissent le résoudre — au même
// titre que window.lastClickedRowId pour "Ligne cliquée (Répéteur)".
window.dispatchGameEvent = function(eventId, elementId, rowId) {
// C'est à CHAQUE écouteur de décider quoi faire ensuite (via sa propre
// suite condition/action, avec ses réglages habituels — cible fixe,
// "Ligne cliquée"...), jamais à l'événement lui-même de le dicter.
window.dispatchGameEvent = function(eventId) {
for (const screenIdKey in gameData.flows) {
const flow = gameData.flows[screenIdKey];
flow.nodes
.filter(function(n){ return n.node_type === 'trigger' && n.trigger_event === 'evenement' && n.trigger_custom_event_id === eventId; })
.forEach(function(n) {
window.lastEventParams = { element_id: elementId, row_id: rowId };
runFlowFrom(n.id, screenIdKey);
});
.forEach(function(n) { runFlowFrom(n.id, screenIdKey); });
}
};