first commit
Build and deploy / build-and-push (push) Successful in 17s
Build and deploy / deploy (push) Successful in 10s

This commit is contained in:
william
2026-08-21 16:23:49 +02:00
committed by william
commit 3f4ebc4527
248 changed files with 10180 additions and 0 deletions
+97
View File
@@ -0,0 +1,97 @@
import html as html_lib
import db
from .style_string import _style_string
from .auto_id import _auto_id
def _hex_to_rgb(hex_color):
hex_color = (hex_color or "#5b8cff").lstrip("#")
if len(hex_color) != 6:
hex_color = "5b8cff"
return tuple(int(hex_color[i:i + 2], 16) for i in (0, 2, 4))
def _interpolate_color(low_hex, high_hex, ratio):
"""Dégradé linéaire simple entre deux couleurs (rouge en bas, vert en
haut par défaut, comme demandé dans le document d'interface de Boîte
Mail) — ratio est déjà borné entre 0 et 1 par l'appelant."""
lo, hi = _hex_to_rgb(low_hex), _hex_to_rgb(high_hex)
mixed = tuple(round(lo[i] + (hi[i] - lo[i]) * ratio) for i in range(3))
return "#{:02x}{:02x}{:02x}".format(*mixed)
def _read_latest_field_value(slug, definition_id, field_name):
"""Lit la valeur du champ demandé sur la ligne la PLUS RÉCENTE de
l'objet visé — même convention que le filtre de Répéteur (1.2) : un
objet utilisé comme jauge de partie (Réputation, Trésorerie...) ne
garde qu'une seule ligne, mise à jour en place."""
if not definition_id or not slug:
return None
definition = db.get_definition(slug, int(definition_id))
if not definition:
return None
rows = db.list_rows(slug, definition)
if not rows:
return None
col = db.slugify(field_name).replace("-", "_")
return rows[0].get(col)
def _render_jauge(el, meta, slug, children_map=None, ctx=None, parent_flex_direction=None):
"""Jauge/barre dont le remplissage (largeur en %) et la couleur (dégradé
bas -> haut) se recalculent automatiquement à partir d'un champ
numérique d'un objet de données — voir 2.1 dans la doc de cadrage Forge
Engine ("binding live d'une donnée vers un style"). Recalculée à chaque
régénération du HTML du jeu (chargement de /play, et après toute action
"Modifier une donnée"), donc toujours à jour sans action supplémentaire
à poser dans le graphe de logique."""
attrs = el.get("attributes") or {}
style = _style_string(el, " overflow:hidden; position:relative; background:#1c2130; border-radius:6px;", parent_flex_direction)
definition_id = attrs.get("_definition_id")
field_name = (attrs.get("_champ") or "").strip()
try:
min_v = float(attrs.get("_min", "0") or 0)
except (TypeError, ValueError):
min_v = 0.0
try:
max_v = float(attrs.get("_max", "100") or 100)
except (TypeError, ValueError):
max_v = 100.0
couleur_basse = attrs.get("_couleur_basse") or "#e05252"
couleur_haute = attrs.get("_couleur_haute") or "#4caf50"
afficher_valeur = attrs.get("_afficher_valeur") == "1"
raw_value = _read_latest_field_value(slug, definition_id, field_name) if field_name else None
try:
value = float(raw_value)
except (TypeError, ValueError):
value = min_v
span = (max_v - min_v) or 1
ratio = max(0.0, min(1.0, (value - min_v) / span))
percent = round(ratio * 100, 1)
fill_color = _interpolate_color(couleur_basse, couleur_haute, ratio)
label_html = ""
if afficher_valeur:
label_html = (
f'<div style="position:absolute; inset:0; display:flex; align-items:center; '
f'justify-content:center; font-family:system-ui,sans-serif; font-size:12px; '
f'color:#e8eaf0; text-shadow:0 1px 2px rgba(0,0,0,.6);">{html_lib.escape(str(raw_value if raw_value is not None else "—"))}</div>'
)
# La classe "jaugeBar" est repérée côté client (play.html, refreshRuntimeData)
# pour savoir que ce wrapper doit être régénéré après toute action
# "Modifier une donnée" — sinon la jauge resterait figée sur sa valeur
# de départ après un changement, exactement comme il fallait déjà
# repérer "repeaterItem" pour les Répéteurs (voir 1.1/1.2).
bar = (
f'<div class="jaugeBar" style="position:absolute; left:0; top:0; bottom:0; width:{percent}%; '
f'background-color:{fill_color}; transition:width .3s ease, background-color .3s ease;"></div>'
)
return (
f'<div id="{_auto_id(el)}" data-element-id="{el["id"]}" style="{html_lib.escape(style)}">'
f'{bar}{label_html}</div>'
)