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>
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>
Étape 1 de la fonctionnalité "Publier un jeu en exécutable autonome" :
un jeu exporté devra fonctionner sans AUCUNE connexion internet, ce qui
suppose d'abord que l'app elle-même n'ait plus aucune dépendance CDN —
Bulma l'était déjà (refonte design system), il ne restait que Google
Fonts et animate.css, chargés par templates/play.html ET
templates/screen_edit.html (aperçu du canevas dans l'éditeur).
GOOGLE_FONTS_LINK (screens/widgets/font_options.py) était une constante
FIXE (4 polices, 2 graisses chacune, jamais dépendante du jeu en cours) :
téléchargées une fois pour toutes (45 fichiers woff2, ~1.1 Mo au total
avec la couverture cyrillique/vietnamienne incluse) dans
static/vendor/fonts/, avec un fonts.css régénéré à partir du CSS
officiel de Google Fonts mais pointant vers les fichiers locaux. Même
chose pour animate.min.css 4.1.1 (static/vendor/animate.min.css).
routes/play/game_play.py et routes/screens/screen_edit.py ne passent
plus google_fonts_link aux templates (devenu inutile, les deux pages
chargent directement les fichiers locaux) ; GOOGLE_FONTS_LINK est retiré
de screens/widgets/font_options.py et screens/__init__.py (plus aucun
appelant).
tests/test_animations.py : les deux tests qui vérifiaient la présence
du lien CDN vérifient maintenant la présence du fichier local
(animate.min.css) — renommés en conséquence.
199 tests toujours verts. Reste à faire pour la fonctionnalité complète :
empaquetage Python portable + serveur minimal + bouton "Publier".
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Le tour précédent exigeait que CHAQUE widget règle sa propre "Donnée
liée" (_data_definition_id) pour voir apparaître le sélecteur de champ -
mais un élément de jeu ("Mail card"...) créé avec un "Objet lié" (voir
element_types.html, bound_definition_id) a précisément pour but d'éviter
ce réglage widget par widget : ses {{champ}} sont censés venir de CET
objet, fourni plus tard par le Répéteur qui l'utilisera comme modèle de
ligne. D'où le bug remonté : un nouveau Titre/Texte posé dans un tel
élément de jeu n'affichait jamais le sélecteur.
Ajoute get_element_type_by_template_screen(slug, screen_id), pour
retrouver depuis l'éditeur d'un écran-modèle l'entrée du catalogue (et
donc l'objet lié) dont il est la recette. screen_edit.py le calcule pour
le panneau de propriétés et le passe à controls_with_values(), qui
l'utilise comme repli pour le champ "Contenu" SEULEMENT si ce widget n'a
pas déjà sa propre "Donnée liée" réglée (priorité conservée au réglage le
plus spécifique).
Ajoute tests/test_element_type_bound_field_picker.py (apparition sans
réglage supplémentaire, absence sans objet lié, priorité à la "Donnée
liée" du widget si réglée).
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>
New "Icône" widget (<i class="...">, color/size customizable exactly like any other widget) plus a curated set of ~250 verified Font Awesome 5 Free solid icon names (fontawesome_icons.py) — not the full ~1500-icon catalog, since an embedded list needs to be guaranteed accurate (a wrong class name silently renders as a blank glyph); any other valid FA5 class still works by typing it directly into the widget's "Icône" setting.
The gallery lives in the (now always-visible) top of the right floating panel, searchable, with each tile both clickable and HTML5-draggable onto the canvas — either action posts to element_add with the chosen icon_class, which now seeds the new element's class instead of leaving it on the generic default. Available in the element-type template editor too, since it reuses screen_edit.html.
Loads Font Awesome 5.15.4 (cdnjs) alongside the existing animate.css/Bulma links, in both the editor and /play.
Verified end to end: gallery renders with real icon glyphs, clicking/simulated-drop creates a correctly-classed <i> element, and the actual /play route renders it with Font Awesome loaded. Full suite green (89).
A Jauge could only target a whole object, not a specific record —
three gauges pointing at the same "value" field (e.g. Réputation/
Trésorerie/Confiance in one "jauge" object) all silently showed the
most recent row's value, with no way to tell them apart.
Add "Enregistrement (ligne) à suivre" (row_id) so a Jauge targets one
specific row, and "Champ contenant le nom" (champ_nom) to show a
label above the bar — both as dropdowns populated from the object's
actual fields/rows (previously "Champ numérique à afficher" was free
text the user had to type correctly by hand). No regression: without
row_id the widget still falls back to the latest row, exactly as
before.
Extract data_definition_options() (rows+fields for a definition) out
of routes/screens/screen_edit.py so controls_with_values.py can reuse
it server-side for the initial render; a small client-side handler
(bindJaugeDefinitionSelect) repopulates the same selects live when
the tracked object is changed without leaving the panel.
Verified live with Playwright: picking "jauge" then "Confiance" then
"value"/"name" in the panel renders an 80%-filled, green-leaning bar
labelled "Confiance" on /play — not the 20%/50% of the other rows.