Deuxième moitié de la fonctionnalité "événements" (voir le commit
précédent pour le backend) : un nouvel onglet "📣 Événements" dans
screen_edit.html (scène ET modèle, même route) pour créer/modifier/
supprimer un événement (nom, description, "a un paramètre élément" oui/
non) et voir où il est déjà écouté/déclenché ; deux nouveaux types de
nœud dans le graphe de logique ; exécution réelle côté client
(play.html).
- templates/screen_edit.html :
- Onglet Événements : barre de création + tableau (patron
form/name="..." + attribut `form="eventEditFormN"` déjà utilisé par
l'onglet Variables du tableau de bord, pour éditer plusieurs champs
d'une ligne sans emboîter un <form> dans un <tr>). Colonne "Utilisé
par" avec liens directs vers les écrans/modèles concernés
(list_custom_event_usages, déjà en place côté backend).
- Nœud Déclencheur "Sur un événement personnalisé" : un simple sélecteur
d'événement (trigger_custom_event_id) — ni élément ni écran, retrouvé
par un scan global côté client (comme "À l'affichage de l'écran").
- Nœud Action "Déclencher un événement" : sélecteur d'événement
(target_custom_event_id), avec élément/ligne concernés affichés
seulement si l'événement a has_element_param (réutilise le sélecteur
d'élément existant + "Ligne cliquée" pour la ligne, pas de définition
d'objet ici donc pas de liste de lignes fixes possible).
- Nœud Action "Modifier un élément" : nouvelle case "Utiliser l'élément
transmis par l'événement en cours" (target_element_from_event) — v1,
seule cette action l'expose (extensible plus tard sans nouveau
changement de schéma).
- nodeLabel()/submitNodeForm()/toggleFlowTriggerFields()/
toggleFlowActionFields() étendus en conséquence, CUSTOM_EVENTS_MAP
(id -> nom/has_element_param) exposé côté JS pour le rendu des
libellés et l'affichage conditionnel des champs paramètre.
- templates/play.html :
- window.dispatchGameEvent(eventId, elementId, rowId) : scan de
gameData.flows (toutes scènes ET modèles à la fois, même principe que
findTriggerNode()/runScreenShowTriggers()) pour trouver chaque
écouteur, pose window.lastEventParams puis exécute son graphe
(runFlowFrom) — au même titre que window.lastClickedRowId pour "Ligne
cliquée".
- runActionNode : nouvelle branche "declencher_evenement" (résout
CLICKED_ROW_ID pour la ligne transmise, comme "Modifier une donnée")
; branche "modifier_element" étendue pour résoudre dynamiquement
l'élément depuis window.lastEventParams quand
target_element_from_event est actif.
- readFieldValue (évaluation de condition) et l'appel serveur de
"Modifier une donnée" (routes/flow/flow_node_run_data.py) résolvent
désormais aussi EVENT_ROW_ID (-2), au même endroit que CLICKED_ROW_ID
(-1) déjà en place.
- routes/screens/screen_edit.py : passe custom_events/
custom_event_usages/custom_events_map_json au template (même patron
que global_variables déjà threadé pour le sélecteur de variable dans
le formulaire de Condition).
Nouveaux tests dans tests/test_custom_events.py : forme exacte du
payload runtime exposé au JS (types entiers, pas des chaînes — une
comparaison stricte "===" échouerait silencieusement sinon),
target_element_from_event bien persisté, résolution serveur d'
EVENT_ROW_ID depuis le corps de la requête. 210 tests au total.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Problème remonté : un déclencheur "Au clic" posé sur un Répéteur exécute
le MÊME graphe pour n'importe quelle ligne cliquée - or une Condition
("Si is_opened est égal à Non") ou une action "Modifier une donnée" ne
pouvaient viser qu'une ligne FIXE, choisie à la création du nœud dans
l'éditeur. Impossible donc de dire "modifie le champ DE LA LIGNE QUE JE
VIENS DE CLIQUER", puisque cette ligne n'est justement jamais connue à
l'avance.
Nouvelle valeur sentinelle CLICKED_ROW_ID (-1, screens/flow/constants.py,
ne collisionne jamais avec un vrai id de ligne) proposée en tête de TOUTE
liste déroulante "Ligne concernée" (clause principale et clauses
supplémentaires d'un nœud Condition, cible d'une action "Modifier une
donnée") : "🖱️ Ligne cliquée (Répéteur)".
Résolution au moment de l'exécution, pas à la création du nœud :
- Condition (évaluée côté client) : readFieldValue() (play.html) résout
-1 en window.lastClickedRowId, déjà capturé par bindClicks() au clic sur
une ligne de Répéteur (déjà utilisé par "Ouvrir la ligne cliquée").
- Action "Modifier une donnée" (exécutée côté serveur) : le client envoie
clicked_row_id dans le corps de la requête POST ; flow_node_run_data.py
ne s'en sert que si le nœud vise justement CLICKED_ROW_ID, sinon la
ligne fixe stockée sur le nœud reste utilisée normalement.
Ajoute tests/test_flow_clicked_row.py (ligne cliquée seule modifiée,
absence de clic = no-op plutôt que plantage, non-régression d'une cible
fixe, présence de l'option dans l'éditeur).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Nouveau panneau "Condition de visibilité" disponible dans les propriétés
de TOUT élément (widget) : permet de masquer un élément en mode jouable
selon deux moyens, au choix -
- une variable globale (nom + type + valeur, une seule par jeu, stockée
dans une nouvelle table _global_variables) ;
- le champ d'un objet de données existant (même convention "état de
partie" - une seule ligne - déjà utilisée par la Jauge).
Une variable ne servant à rien si elle ne peut jamais changer en cours de
partie, ajoute aussi une nouvelle action de flow "Modifier une variable
globale" (parallèle à "Modifier une donnée"), avec sa propre route
d'exécution serveur et son sous-formulaire dans l'éditeur de logique de
scène. Une variable peut aussi se créer à la volée depuis le sélecteur du
panneau de visibilité, sans quitter les propriétés de l'élément.
La condition n'est évaluée qu'en mode jouable (/game/<slug>/play), jamais
dans l'éditeur, pour que l'élément reste toujours sélectionnable. Un
élément masqué se réévalue en direct après toute action "Modifier une
donnée/variable", via le même mécanisme de rafraîchissement déjà utilisé
par la Jauge et le Répéteur.
Corrige au passage deux bugs découverts en testant bout en bout : (1)
apply_ctx plantait sur le nouveau marqueur interne _forge_play_mode (un
booléen parmi les {{champ}} à substituer, qui attend des chaînes) ; (2)
_compare traitait toute valeur booléenne stockée en chaîne ("0" inclus,
donc toujours vraie en Python) comme vraie - correct pour les champs
d'objet (entiers SQLite) mais faux pour les variables globales (toujours
stockées en texte).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>