Let Texte/Titre elements bind directly to a single row of another object
Adds a "Donnée liée" settings group to Texte/Titre: pick an object, optionally match it to the current game state via 1-2 filter conditions (same engine as the data-repeater's filter, including {{Objet.champ}} cross-references), and use {{champ}} in the text content to show a field from that one matching row. Unlike the data repeater — built for showing a list of rows — this covers displaying a single computed value (e.g. the objective of the level matching the game's current parcours/level) without wrapping it in a repeater.
This commit is contained in:
@@ -14,6 +14,7 @@ from .render_repeater import _render_repeater
|
|||||||
from .render_jauge import _render_jauge
|
from .render_jauge import _render_jauge
|
||||||
from .render_overlay import _render_overlay
|
from .render_overlay import _render_overlay
|
||||||
from .render_onglets import _render_onglets
|
from .render_onglets import _render_onglets
|
||||||
|
from .resolve_bound_row import _resolve_bound_row_ctx
|
||||||
|
|
||||||
|
|
||||||
def render_element_html(el, slug=None, children_map=None, ctx=None, parent_flex_direction=None):
|
def render_element_html(el, slug=None, children_map=None, ctx=None, parent_flex_direction=None):
|
||||||
@@ -29,6 +30,9 @@ def render_element_html(el, slug=None, children_map=None, ctx=None, parent_flex_
|
|||||||
pose physiquement cet élément, s'il y en a un — elle influence la largeur
|
pose physiquement cet élément, s'il y en a un — elle influence la largeur
|
||||||
par défaut de cet élément (voir _style_string)."""
|
par défaut de cet élément (voir _style_string)."""
|
||||||
meta = widget_meta(el.get("widget"))
|
meta = widget_meta(el.get("widget"))
|
||||||
|
bound_ctx = _resolve_bound_row_ctx(slug, el.get("attributes") or {})
|
||||||
|
if bound_ctx:
|
||||||
|
ctx = {**(ctx or {}), **bound_ctx}
|
||||||
special = meta.get("special_render")
|
special = meta.get("special_render")
|
||||||
if special == "table":
|
if special == "table":
|
||||||
return _render_table(el, meta, slug, children_map, ctx, parent_flex_direction)
|
return _render_table(el, meta, slug, children_map, ctx, parent_flex_direction)
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import db
|
||||||
|
|
||||||
|
from .filter_repeater_rows import filter_repeater_rows
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_bound_row_ctx(slug, attrs):
|
||||||
|
"""Si cet élément est lié à un objet de données (attr:_data_definition_id —
|
||||||
|
réglage "Lier à un objet de données" des widgets Texte/Titre, voir
|
||||||
|
DATA_BINDING_CONTROLS), retrouve LA ligne qui correspond aux 1 ou 2
|
||||||
|
filtres réglés (_data_filtre*/_data_filtre2*) — même moteur que le
|
||||||
|
filtre du Répéteur de données, voir filter_repeater_rows.py — et
|
||||||
|
renvoie ses champs sous forme de {nom_du_champ: valeur en texte},
|
||||||
|
exploitable par {{champ}} dans le contenu. Permet d'afficher UNE seule
|
||||||
|
valeur calculée (ex: l'objectif du niveau en cours) sans avoir besoin de
|
||||||
|
poser un Répéteur, qui affiche lui plusieurs lignes. Renvoie None si
|
||||||
|
rien n'est lié ou si aucune ligne ne correspond."""
|
||||||
|
definition_id = attrs.get("_data_definition_id")
|
||||||
|
if not definition_id or not slug:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
definition = db.get_definition(slug, int(definition_id))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return None
|
||||||
|
if not definition:
|
||||||
|
return None
|
||||||
|
rows = db.list_rows(slug, definition)
|
||||||
|
rows = filter_repeater_rows(slug, definition, rows, {
|
||||||
|
"_filtre_champ": attrs.get("_data_filtre_champ"),
|
||||||
|
"_filtre_operateur": attrs.get("_data_filtre_operateur"),
|
||||||
|
"_filtre_valeur": attrs.get("_data_filtre_valeur"),
|
||||||
|
"_filtre2_champ": attrs.get("_data_filtre2_champ"),
|
||||||
|
"_filtre2_operateur": attrs.get("_data_filtre2_operateur"),
|
||||||
|
"_filtre2_valeur": attrs.get("_data_filtre2_valeur"),
|
||||||
|
})
|
||||||
|
if not rows:
|
||||||
|
return None
|
||||||
|
row = rows[0]
|
||||||
|
return {f["name"]: str(row.get(db.slugify(f["name"]).replace("-", "_"), "") or "") for f in definition["fields"]}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
from ..controls.c_text import c_text
|
||||||
|
from ..controls.c_select import c_select
|
||||||
|
from ...flow.constants import CONDITION_OPERATORS
|
||||||
|
|
||||||
|
# Permet à un Texte/Titre d'afficher directement la valeur d'un champ d'un
|
||||||
|
# AUTRE objet de données, sans avoir besoin de le poser dans un Répéteur —
|
||||||
|
# utile pour une valeur unique calculée (ex: l'objectif du niveau en cours)
|
||||||
|
# plutôt qu'une liste. La ligne affichée est celle qui correspond aux 1 ou 2
|
||||||
|
# filtres réglés ci-dessous (mêmes réglages, même moteur que ceux du
|
||||||
|
# Répéteur de données — voir filter_repeater_rows.py) ; sans filtre, c'est la
|
||||||
|
# ligne la plus récente de l'objet choisi (convention déjà utilisée par la
|
||||||
|
# Jauge pour un objet "état de partie" qui ne garde qu'une seule ligne).
|
||||||
|
DATA_BINDING_CONTROLS = [
|
||||||
|
c_select("data_definition_id", "Lier à un objet de données (pour {{champ}} ci-dessus)",
|
||||||
|
"attr:_data_definition_id", [("", "(sélectionner un objet)")], default="", group="Donnée liée"),
|
||||||
|
c_text("data_filtre_champ", "Trouver la ligne où ce champ",
|
||||||
|
"attr:_data_filtre_champ", placeholder="ex. number", default="", group="Donnée liée"),
|
||||||
|
c_select("data_filtre_operateur", "...est", "attr:_data_filtre_operateur", CONDITION_OPERATORS, default="egal", group="Donnée liée"),
|
||||||
|
c_text("data_filtre_valeur", "...cette valeur, ou {{NomObjet.champ}} pour une donnée qui change en cours de partie",
|
||||||
|
"attr:_data_filtre_valeur", placeholder="ex. 3 ou {{game.current_level}}", default="", group="Donnée liée"),
|
||||||
|
c_text("data_filtre2_champ", "ET (optionnel) : ce 2ᵉ champ",
|
||||||
|
"attr:_data_filtre2_champ", placeholder="ex. parcour_id", default="", group="Donnée liée"),
|
||||||
|
c_select("data_filtre2_operateur", "...est", "attr:_data_filtre2_operateur", CONDITION_OPERATORS, default="egal", group="Donnée liée"),
|
||||||
|
c_text("data_filtre2_valeur", "...cette valeur, ou {{NomObjet.champ}}",
|
||||||
|
"attr:_data_filtre2_valeur", placeholder="ex. 1 ou {{game.current_parcours}}", default="", group="Donnée liée"),
|
||||||
|
]
|
||||||
@@ -24,7 +24,7 @@ def controls_with_values(slug, el):
|
|||||||
result = []
|
result = []
|
||||||
for control in meta.get("controls", []):
|
for control in meta.get("controls", []):
|
||||||
control = dict(control)
|
control = dict(control)
|
||||||
if control["key"] == "definition_id":
|
if control["key"] in ("definition_id", "data_definition_id"):
|
||||||
defs = db.list_definitions(slug)
|
defs = db.list_definitions(slug)
|
||||||
control["options"] = [("", "(sélectionner un objet)")] + [(str(d["id"]), d["name"]) for d in defs]
|
control["options"] = [("", "(sélectionner un objet)")] + [(str(d["id"]), d["name"]) for d in defs]
|
||||||
if control["key"] == "template_type_id":
|
if control["key"] == "template_type_id":
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from .controls.c_toggle_token import c_toggle_token
|
|||||||
from .controls.c_align import c_align
|
from .controls.c_align import c_align
|
||||||
from .controls.c_select import c_select
|
from .controls.c_select import c_select
|
||||||
from .control_groups.text_style_controls_extra import TEXT_STYLE_CONTROLS_EXTRA
|
from .control_groups.text_style_controls_extra import TEXT_STYLE_CONTROLS_EXTRA
|
||||||
|
from .control_groups.data_binding_controls import DATA_BINDING_CONTROLS
|
||||||
from .font_options import FONT_OPTIONS
|
from .font_options import FONT_OPTIONS
|
||||||
from ..flow.constants import CONDITION_OPERATORS
|
from ..flow.constants import CONDITION_OPERATORS
|
||||||
|
|
||||||
@@ -16,7 +17,8 @@ WIDGETS = {
|
|||||||
"label": "Texte", "icon": "📝", "tag": "p", "has_content": True,
|
"label": "Texte", "icon": "📝", "tag": "p", "has_content": True,
|
||||||
"w": 40, "h": 10, "default_content": "Texte du paragraphe.",
|
"w": 40, "h": 10, "default_content": "Texte du paragraphe.",
|
||||||
"controls": [
|
"controls": [
|
||||||
c_textarea("content", "Texte", "content", default="Texte du paragraphe.", group="Contenu"),
|
c_textarea("content", "Texte — {{champ}} affiche la valeur de ce champ sur la ligne trouvée ci-dessous", "content", default="Texte du paragraphe.", group="Contenu"),
|
||||||
|
] + DATA_BINDING_CONTROLS + [
|
||||||
c_slider("font-size", "Taille du texte", "style:font-size", min=10, max=64, default=16, group="Texte"),
|
c_slider("font-size", "Taille du texte", "style:font-size", min=10, max=64, default=16, group="Texte"),
|
||||||
c_color("color", "Couleur du texte", "style:color", "#e8eaf0", group="Texte"),
|
c_color("color", "Couleur du texte", "style:color", "#e8eaf0", group="Texte"),
|
||||||
c_align("text-align", "Alignement", "style:text-align", group="Texte"),
|
c_align("text-align", "Alignement", "style:text-align", group="Texte"),
|
||||||
@@ -30,9 +32,10 @@ WIDGETS = {
|
|||||||
"label": "Titre", "icon": "🔠", "tag": None, "has_content": True,
|
"label": "Titre", "icon": "🔠", "tag": None, "has_content": True,
|
||||||
"w": 55, "h": 10, "default_content": "Titre",
|
"w": 55, "h": 10, "default_content": "Titre",
|
||||||
"controls": [
|
"controls": [
|
||||||
c_textarea("content", "Texte", "content", default="Titre", group="Contenu"),
|
c_textarea("content", "Texte — {{champ}} affiche la valeur de ce champ sur la ligne trouvée ci-dessous", "content", default="Titre", group="Contenu"),
|
||||||
c_select("niveau", "Importance du titre", "attr:_niveau",
|
c_select("niveau", "Importance du titre", "attr:_niveau",
|
||||||
[("1", "Très grand — titre principal"), ("2", "Grand"), ("3", "Moyen")], default="2", group="Contenu"),
|
[("1", "Très grand — titre principal"), ("2", "Grand"), ("3", "Moyen")], default="2", group="Contenu"),
|
||||||
|
] + DATA_BINDING_CONTROLS + [
|
||||||
c_slider("font-size", "Taille du texte", "style:font-size", min=14, max=80, default=32, group="Texte"),
|
c_slider("font-size", "Taille du texte", "style:font-size", min=14, max=80, default=32, group="Texte"),
|
||||||
c_color("color", "Couleur du texte", "style:color", "#e8eaf0", group="Texte"),
|
c_color("color", "Couleur du texte", "style:color", "#e8eaf0", group="Texte"),
|
||||||
c_align("text-align", "Alignement", "style:text-align", default="left", group="Texte"),
|
c_align("text-align", "Alignement", "style:text-align", default="left", group="Texte"),
|
||||||
|
|||||||
Reference in New Issue
Block a user