Files
Forge-Engine/screens/rendering/visibility_condition.py
T
williamandClaude Sonnet 5 aaf9954446 Variables globales Objet/Tableau, lisibles via un chemin dans les conditions et filtres
Deux nouveaux types de variable globale (db/constants.py) : "objet" et
"tableau" — valeur stockée en JSON (colonne TEXT existante), avec
validation à la création/modification (db/global_vars/
coerce_structured_value.py) : un JSON invalide retombe sur un défaut sûr
("{}"/"[]") plutôt que de corrompre silencieusement la variable pour
toutes ses lectures suivantes. apply_variable_action.py n'a besoin
d'aucun changement — "definir_texte" écrit déjà n'importe quelle chaîne
telle quelle.

Nouvelle résolution de chemin, partagée (screens/rendering/
filter_repeater_rows.py::_resolve_variable_path) : navigue dans la
valeur JSON d'une variable selon un chemin ".champ"/"[index]" chaînable
(ex. ".arme.degats", "[0].valeur") — ne lève jamais, renvoie None si le
JSON est invalide ou qu'un segment du chemin ne correspond à rien.
Branchée à deux endroits, qui lisaient déjà une variable globale :

- La valeur de comparaison {{$nom_variable}} (filtres de Répéteur ET
  Condition de visibilité, qui partagent le même
  _resolve_filter_value()) accepte maintenant un chemin optionnel :
  {{$perso.nom}}, {{$scores[0]}}. Le sélecteur "Variable globale" du
  panneau de propriétés (screen_edit.html, .filterValueVariable) gagne un
  champ "Chemin optionnel" à côté du choix de variable — même regex
  étendue côté JS (_VAR_REF_RE) que côté Python (_VAR_REF_PATTERN), pour
  que la valeur round-trip correctement à la réouverture du panneau.

- La variable VÉRIFIÉE par une Condition de visibilité en mode "variable"
  (choisie via un <select>, pas la syntaxe {{$...}}) gagne son propre
  nouveau contrôle "Chemin dans la variable" (visibility_condition_
  controls.py) — nécessaire pour comparer un champ d'un Objet ou un
  élément d'un Tableau, pas seulement la variable entière.

game_dashboard.html (onglet Variables) : la valeur par défaut de la barre
de création devient un <textarea> (fonctionne aussi bien pour un JSON
multi-ligne qu'un scalaire court), et la cellule "Valeur" du tableau des
variables existantes devient un <textarea> quand le type est objet/
tableau.

4 nouveaux tests (tests/test_variable_object_array.py) : validation JSON
à la création, filtre de Répéteur avec chemin chaîné, condition de
visibilité avec accès par index de tableau, chemin invalide/absent sans
plantage.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 14:28:23 +02:00

69 lines
3.5 KiB
Python

import db
from .filter_repeater_rows import _resolve_filter_value, _resolve_variable_path, _compare, _field_column
def is_element_visible(slug, attrs, ctx=None):
"""Condition de visibilité (panneau de propriétés, tout widget — voir
VISIBILITY_CONDITION_CONTROLS) : masque un élément selon soit une
VARIABLE GLOBALE (une seule valeur pour tout le jeu, voir
db/global_vars/), soit le champ d'un objet de données. Absente de
réglage (_visibilite_cond_mode vide) => toujours visible, comportement
inchangé pour tous les éléments déjà créés. Réutilise
_compare/_resolve_filter_value/_field_column de filter_repeater_rows.py
pour rester cohérent avec les mêmes opérateurs et la même syntaxe
"{{Objet.champ}}" pour une valeur de comparaison dynamique.
`ctx` porte les {{champ}} de la ligne en cours (voir render_repeater.py)
quand cet élément est descendant d'un Répéteur de données — en mode
"objet", si le champ réglé s'y trouve déjà, sa valeur est utilisée telle
quelle plutôt que d'aller chercher la ligne la plus récente de l'objet
en base : sans ça, TOUS les exemplaires d'un même modèle de ligne (ex.
une "Mail card" répétée pour chaque e-mail) évalueraient la même ligne
(la plus récente de l'objet) au lieu de chacun la sienne, et
n'afficheraient donc jamais rien de cohérent. Hors Répéteur (mode
"objet" sur un élément posé directement sur l'écran), le champ n'est
pas dans ctx et on retombe sur la convention "1 seule ligne = état de
partie" (déjà utilisée par la Jauge/Donnée liée) : la ligne la plus
récente de l'objet choisi."""
mode = attrs.get("_visibilite_cond_mode")
if mode not in ("variable", "objet"):
return True
operator = attrs.get("_visibilite_cond_operateur") or "egal"
expected = _resolve_filter_value(slug, attrs.get("_visibilite_cond_valeur") or "")
if mode == "variable":
var_name = attrs.get("_visibilite_cond_variable")
if not var_name:
return True
variable = db.get_global_variable(slug, var_name)
if not variable:
return True # variable supprimée depuis : ne bloque jamais l'affichage
value = variable.get("value")
# "Chemin dans la variable" (voir visibility_condition_controls.py)
# : nécessaire pour comparer un CHAMP d'une variable "objet" ou un
# ÉLÉMENT d'une variable "tableau", pas seulement la variable
# entière — même syntaxe ".champ"/"[index]" que {{$var.chemin}}.
path = attrs.get("_visibilite_cond_variable_chemin")
if path:
value = _resolve_variable_path(value, path)
return _compare(value, operator, expected, variable.get("type") or "texte")
definition_id = attrs.get("_visibilite_cond_definition_id")
field_name = attrs.get("_visibilite_cond_champ")
if not definition_id or not field_name:
return True
definition = db.get_definition(slug, int(definition_id))
if not definition:
return True
field_type = next((f["type"] for f in definition["fields"] if f["name"] == field_name), "texte")
if ctx and field_name in ctx:
return _compare(ctx.get(field_name), operator, expected, field_type)
rows = db.list_rows(slug, definition)
if not rows:
return True # objet sans donnée pour l'instant : ne bloque pas
row = rows[0] # convention "état de partie" : une seule ligne, voir data_definition_options.py
return _compare(row.get(_field_column(definition, field_name)), operator, expected, field_type)