New widget where each tab is a real "conteneur" element posed as a child (see screens/elements/add_tab.py) — this reuses everything that already exists for a normal container (adding a Répéteur/Conteneur/etc. inside via "Ajouter DANS ce conteneur", renaming to change the tab's visible label, deleting via the standard trash icon) instead of inventing a separate storage format for tabs. The widget's own properties panel gets a dedicated "Onglets" section to add a tab, rename one, jump to its content, or delete it. Rendering (render_onglets.py) builds a tab bar + one panel per tab, switched client-side (forgeShowTab, in both screen_edit.html and play.html) with only one panel visible at a time. Distinct from the existing "activer_onglet" flow action (2.3, manual show-one/hide-siblings) — that stays available for custom show/hide wiring; this widget is the turnkey version with tab management built in. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
80 lines
4.0 KiB
Python
80 lines
4.0 KiB
Python
import html as html_lib
|
|
|
|
from ..widgets.widget_meta import widget_meta
|
|
from .visible_attrs import _visible_attrs
|
|
from .style_string import _style_string
|
|
from .attr_string import _attr_string
|
|
from .apply_ctx import _apply_ctx
|
|
from .render_children import _render_children
|
|
from .render_table import _render_table
|
|
from .render_select import _render_select
|
|
from .render_fieldset import _render_fieldset
|
|
from .render_checkbox_or_radio import _render_checkbox_or_radio
|
|
from .render_repeater import _render_repeater
|
|
from .render_jauge import _render_jauge
|
|
from .render_overlay import _render_overlay
|
|
from .render_onglets import _render_onglets
|
|
|
|
|
|
def render_element_html(el, slug=None, children_map=None, ctx=None, parent_flex_direction=None):
|
|
"""Construit le HTML réel (balise + attributs + style + contenu, y
|
|
compris les éléments physiquement posés à l'intérieur pour un
|
|
conteneur/répéteur/groupe de champs) — utilisé par l'éditeur (canevas)
|
|
et le mode jouable. N'écrit jamais rien sur disque : uniquement du HTML
|
|
généré à la volée à partir de ce qui est stocké en base. `slug` est
|
|
nécessaire pour le Répéteur de données (il doit lire les données du jeu
|
|
au moment de l'affichage). `ctx` porte les valeurs {{champ}} de la ligne
|
|
en cours quand cet élément est descendant d'un répéteur. `parent_flex_direction`
|
|
est la disposition interne (voir _parent_flex_direction) du conteneur qui
|
|
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)."""
|
|
meta = widget_meta(el.get("widget"))
|
|
special = meta.get("special_render")
|
|
if special == "table":
|
|
return _render_table(el, meta, slug, children_map, ctx, parent_flex_direction)
|
|
if special == "select_options":
|
|
return _render_select(el, meta, slug, children_map, ctx, parent_flex_direction)
|
|
if special == "fieldset_legend":
|
|
return _render_fieldset(el, meta, slug, children_map, ctx, parent_flex_direction)
|
|
if special == "checkbox_label":
|
|
return _render_checkbox_or_radio(el, meta, slug, children_map, ctx, parent_flex_direction)
|
|
if special == "repeater":
|
|
return _render_repeater(el, meta, slug, children_map, ctx, parent_flex_direction)
|
|
if special == "jauge":
|
|
return _render_jauge(el, meta, slug, children_map, ctx, parent_flex_direction)
|
|
if special == "overlay":
|
|
return _render_overlay(el, meta, slug, children_map, ctx, parent_flex_direction)
|
|
if special == "onglets":
|
|
return _render_onglets(el, meta, slug, children_map, ctx, parent_flex_direction)
|
|
|
|
tag = meta.get("tag")
|
|
if tag is None: # widget à balise dynamique (ex: "titre" -> h1/h2/h3)
|
|
niveau = (el.get("attributes") or {}).get("_niveau", "2")
|
|
tag = f"h{niveau}"
|
|
|
|
attrs = _visible_attrs(el, meta, ctx)
|
|
style = _style_string(el, parent_flex_direction=parent_flex_direction, widget_key=el.get("widget"))
|
|
|
|
# 3.1 (Confort) — interactions au survol : un texte de remplacement
|
|
# optionnel, affiché à la place du contenu normal quand le joueur
|
|
# survole cet élément à la souris (voir bindHoverTexts() dans
|
|
# templates/play.html). Absent par défaut => aucun changement, donc
|
|
# aucune régression sur les éléments déjà créés.
|
|
hover_text = (el.get("attributes") or {}).get("_survol_texte")
|
|
if hover_text:
|
|
attrs["data-hover-text"] = _apply_ctx(hover_text, ctx)
|
|
|
|
if meta.get("self_closing"):
|
|
return f"<{tag} {_attr_string(attrs, style)}>"
|
|
|
|
content = _apply_ctx(el.get("content") or "", ctx)
|
|
child_html = _render_children(el, slug, children_map, ctx)
|
|
|
|
if tag in ("ul", "ol"):
|
|
items = [line.strip() for line in content.split("\n") if line.strip()]
|
|
inner = "".join(f"<li>{html_lib.escape(item)}</li>" for item in items) + child_html
|
|
return f"<{tag} {_attr_string(attrs, style)}>{inner}</{tag}>"
|
|
|
|
safe_content = html_lib.escape(content).replace("\n", "<br>") if content else ""
|
|
return f"<{tag} {_attr_string(attrs, style)}>{safe_content}{child_html}</{tag}>"
|