Deux fonctionnalités demandées, développées et corrigées dans cet
échange :
1. "Donnée liée" (Texte/Titre) : le réglage à 2 filtres fixes (toujours
combinés en ET) devient une liste de conditions ILLIMITÉE, avec un
choix ET/OU pour les combiner (screens/widgets/controls/c_clause_list.py,
screens/clause_list_codec.py). Rétrocompatible avec les anciens
éléments (_data_filtre_champ/_data_filtre2_champ), convertis à la
volée à la lecture, sans migration. Après un premier essai à la
présentation trop compacte et technique (retour utilisateur : "pas de
champ technique, pas de notation bizarre {{ }}"), la présentation
finale reprend EXACTEMENT l'ancien style (labels "Champ"/"...est"/
"...cette valeur", même sélecteur de valeur fixe/dynamique/variable
déjà existant, jamais la syntaxe brute), simplement répétée par
condition (templates/partials/clause_row.html), avec un bouton
"+ Ajouter une condition" bien visible et une liste scrollable
(static/style.css, .clauseListWrap). Le même moteur (filter_repeater_
rows.py généralisé) profite aussi au Répéteur de données en interne.
2. Nœud Condition de la Logique de la scène : peut désormais tester une
VARIABLE GLOBALE en plus d'un champ d'objet (cond_source/cond_variable/
cond_variable_chemin — screens/flow/ensure_flow_schema.py), sur la
clause principale ET chaque clause supplémentaire (ET/OU). Évalué côté
CLIENT (templates/play.html, evaluateConditionClause), contre un
nouveau gameData.variables exposé par full_game_payload.py — tenu à
jour par refreshRuntimeData() après toute action qui modifie une
variable, sans changement supplémentaire nécessaire. Le panneau de
condition reste utilisable même sans aucun objet défini dans le jeu
(avant, il disparaissait entièrement).
Vérifié : 153 tests pytest (nouveaux : test_data_binding_clause_list.py,
test_condition_variable.py) + logique JS d'évaluation des conditions
vérifiée isolément avec Node (variable scalaire, objet avec chemin
chaîné, tableau par index, variable introuvable, booléen, rétrocompatibilité
legacy) + rendu des deux pages (éditeur/jeu) vérifié sur le vrai projet
"test" en plus des jeux de test.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
83 lines
4.3 KiB
Python
83 lines
4.3 KiB
Python
import db
|
|
|
|
from ..screens_repo.list_screens import list_screens
|
|
from ..elements.list_elements import list_elements
|
|
from ..flow.list_flow_nodes import list_flow_nodes
|
|
from ..flow.list_flow_edges import list_flow_edges
|
|
from ..animations.list_animation_clips import list_animation_clips
|
|
from ..element_types.list_element_types import list_element_types
|
|
|
|
|
|
def full_game_payload(slug):
|
|
"""Toutes les données nécessaires au runtime JS de la page de jeu jouable
|
|
(/game/<slug>/play) : chaque écran avec ses éléments (HTML déjà généré,
|
|
y compris les Répéteurs de données, lus en direct), le graphe de logique
|
|
(nœuds + fils) de chaque écran, et un instantané des données du jeu
|
|
(avec le type de chaque champ, nécessaire pour évaluer une condition
|
|
correctement) — un seul aller-retour serveur, ensuite tout se joue côté
|
|
client (aucune navigation ne recharge la page, sauf pour appliquer une
|
|
action "modifier une donnée", qui doit passer par le serveur)."""
|
|
screens_ = list_screens(slug)
|
|
payload_screens = []
|
|
flows = {}
|
|
animations = {}
|
|
for s in screens_:
|
|
elements = list_elements(slug, s["id"], enforce_visibility=True)
|
|
payload_screens.append({**s, "elements": elements})
|
|
|
|
# Le graphe de logique et les animations sont, eux, lus pour TOUS les
|
|
# écrans, écrans-modèles compris (include_templates=True) — pas
|
|
# seulement les "vrais" écrans ci-dessus. Un élément de jeu réutilisable
|
|
# (ex. "mail content") peut avoir son propre déclencheur/sa propre
|
|
# animation posée dans l'éditeur de SON écran-modèle (voir
|
|
# screen_edit.html) : sans ses entrées ici, gameData.flows/
|
|
# gameData.animations n'auraient jamais contenu cet écran-modèle côté
|
|
# client, et findTriggerNode()/collectAnimationClips() (templates/
|
|
# play.html) — pourtant déjà écrits pour les chercher — n'auraient
|
|
# jamais rien trouvé. On ne les ajoute PAS à payload_screens : un
|
|
# écran-modèle ne doit jamais être rendu comme un vrai <div
|
|
# class="playScreen"> (il n'est jamais affiché tel quel, seulement
|
|
# rechargé en direct à l'intérieur d'un élément qui l'utilise).
|
|
for s in list_screens(slug, include_templates=True):
|
|
flows[str(s["id"])] = {
|
|
"nodes": list_flow_nodes(slug, s["id"]),
|
|
"edges": list_flow_edges(slug, s["id"]),
|
|
}
|
|
animations[str(s["id"])] = list_animation_clips(slug, s["id"])
|
|
|
|
definitions = db.list_definitions(slug)
|
|
data = {}
|
|
fields_meta = {}
|
|
for d in definitions:
|
|
full = db.get_definition(slug, d["id"])
|
|
rows = db.list_rows(slug, full)
|
|
cols = {f["name"]: db.slugify(f["name"]).replace("-", "_") for f in full["fields"]}
|
|
data[str(d["id"])] = [
|
|
{**{fname: r.get(col) for fname, col in cols.items()}, "id": r["id"]}
|
|
for r in rows
|
|
]
|
|
fields_meta[str(d["id"])] = [{"name": f["name"], "type": f["type"]} for f in full["fields"]]
|
|
|
|
# element_type_id -> id de son écran-modèle : nécessaire côté client (voir
|
|
# collectAnimationClips() dans play.html) pour savoir, quand un élément de
|
|
# jeu réutilisable (ex. "mail content") est posé sur un écran, quels
|
|
# autres clips d'animation (ceux de SON PROPRE écran-modèle) doivent
|
|
# aussi être joués à l'affichage de cet écran — sans quoi une animation
|
|
# posée directement dans l'éditeur du modèle ne se jouait jamais quand le
|
|
# modèle est utilisé ailleurs.
|
|
element_types = {str(t["id"]): t["template_screen_id"] for t in list_element_types(slug)}
|
|
|
|
# Instantané des variables globales, pour qu'un nœud Condition puisse en
|
|
# tester une côté CLIENT (evaluateConditionClause, templates/play.html) —
|
|
# {{$var}} (Répéteur/Condition de visibilité), lui, reste résolu côté
|
|
# SERVEUR au rendu (filter_repeater_rows.py) et n'a jamais eu besoin de
|
|
# ça. refreshRuntimeData() récupère un payload entier (donc des
|
|
# variables à jour) après toute action qui en modifie une.
|
|
variables = {v["name"]: {"value": v["value"], "type": v["type"]} for v in db.list_global_variables(slug)}
|
|
|
|
return {
|
|
"screens": payload_screens, "flows": flows, "animations": animations,
|
|
"element_types": element_types, "data": data, "fields_meta": fields_meta,
|
|
"variables": variables,
|
|
}
|