Add an Onglets (Tabs) widget
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>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
2b2619d1c4
commit
17fa4cf087
@@ -73,6 +73,7 @@ from .screens_repo.set_screen_aspect import set_screen_aspect
|
||||
from .elements.list_elements import list_elements
|
||||
from .elements.get_element import get_element
|
||||
from .elements.add_element import add_element
|
||||
from .elements.add_tab import add_tab
|
||||
from .elements.update_element_geometry import update_element_geometry
|
||||
from .elements.save_element_controls import save_element_controls
|
||||
from .elements.delete_element import delete_element
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import db
|
||||
|
||||
from .add_element import add_element
|
||||
from .rename_element import rename_element
|
||||
|
||||
|
||||
def add_tab(slug, onglets_id, screen_id):
|
||||
"""Ajoute un nouvel onglet à un élément "onglets" : un simple conteneur
|
||||
posé comme enfant (voir render_onglets.py), nommé "Onglet N" par défaut
|
||||
(N = son rang) — ce nom est directement ce qui s'affiche sur l'onglet,
|
||||
modifiable ensuite comme celui de n'importe quel élément."""
|
||||
conn = db.connect(slug)
|
||||
count = conn.execute(
|
||||
"SELECT COUNT(*) AS c FROM _screen_elements WHERE parent_id = ?", (onglets_id,)
|
||||
).fetchone()["c"]
|
||||
conn.close()
|
||||
tab_id = add_element(slug, screen_id, "conteneur", parent_id=onglets_id)
|
||||
rename_element(slug, tab_id, f"Onglet {count + 1}")
|
||||
return tab_id
|
||||
@@ -13,6 +13,7 @@ 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):
|
||||
@@ -43,6 +44,8 @@ def render_element_html(el, slug=None, children_map=None, ctx=None, parent_flex_
|
||||
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)
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import html as html_lib
|
||||
|
||||
from .visible_attrs import _visible_attrs
|
||||
from .style_string import _style_string
|
||||
from .parent_flex_direction import _parent_flex_direction
|
||||
from .auto_id import _auto_id
|
||||
from .attr_string import _attr_string
|
||||
|
||||
|
||||
def _render_onglets(el, meta, slug, children_map=None, ctx=None, parent_flex_direction=None):
|
||||
"""Élément à onglets : chaque onglet est un vrai conteneur posé comme
|
||||
enfant (voir screens/elements/add_tab.py) — ça réutilise tel quel tout
|
||||
ce qui existe déjà pour un conteneur normal (ajouter du contenu dedans
|
||||
via "Ajouter DANS ce conteneur", le renommer pour changer le libellé
|
||||
affiché sur l'onglet, le supprimer via la poubelle standard) plutôt que
|
||||
d'inventer un stockage séparé pour les onglets. Un seul panneau visible
|
||||
à la fois, basculé par un petit script embarqué qui marche aussi bien
|
||||
dans l'éditeur qu'en jeu (voir forgeShowTab dans screen_edit.html et
|
||||
play.html)."""
|
||||
from .render_element_html import render_element_html
|
||||
|
||||
tabs = (children_map or {}).get(el["id"], [])
|
||||
attrs = _visible_attrs(el, meta, ctx)
|
||||
style = _style_string(el, " display:flex; flex-direction:column;", parent_flex_direction)
|
||||
group_id = _auto_id(el)
|
||||
|
||||
if not tabs:
|
||||
return (
|
||||
f'<div {_attr_string(attrs, style)}>'
|
||||
f'<em style="opacity:.6;">Onglets — ajoute au moins un onglet dans ses réglages.</em></div>'
|
||||
)
|
||||
|
||||
flex_dir = _parent_flex_direction(el)
|
||||
bar_items = []
|
||||
panels = []
|
||||
for i, tab in enumerate(tabs):
|
||||
label = tab.get("name") or f"Onglet {i + 1}"
|
||||
active = i == 0
|
||||
tab_html = render_element_html(tab, slug, children_map, ctx, parent_flex_direction=flex_dir)
|
||||
target_id = f"tabpanel-{tab['id']}"
|
||||
bar_items.append(
|
||||
f'<button type="button" class="forgeTabBtn{" active" if active else ""}" '
|
||||
f'data-tab-btn="{group_id}" data-tab-target="{target_id}" '
|
||||
f'onclick="forgeShowTab(this)">{html_lib.escape(str(label))}</button>'
|
||||
)
|
||||
hidden_style = "" if active else ' style="display:none;"'
|
||||
panels.append(
|
||||
f'<div class="forgeTabPanel" id="{target_id}" data-tab-group="{group_id}"{hidden_style}>'
|
||||
f'{tab_html}</div>'
|
||||
)
|
||||
|
||||
return (
|
||||
f'<div {_attr_string(attrs, style)}>'
|
||||
f'<div class="forgeTabBar">{"".join(bar_items)}</div>'
|
||||
f'<div class="forgeTabPanels" style="flex:1 1 auto; min-height:0;">{"".join(panels)}</div>'
|
||||
f"</div>"
|
||||
)
|
||||
@@ -277,4 +277,14 @@ WIDGETS = {
|
||||
c_slider("arrondi", "Arrondi des coins de la boîte", "attr:_arrondi", unit="", min=0, max=40, default=12, group="Apparence"),
|
||||
],
|
||||
},
|
||||
"onglets": {
|
||||
# Chaque onglet est un vrai élément "conteneur" posé comme enfant
|
||||
# (voir screens/elements/add_tab.py et render_onglets.py) — pas de
|
||||
# gestion dédiée ici : ajouter/renommer/supprimer un onglet se fait
|
||||
# via le bloc "Onglets" du panneau de propriétés (screen_edit.html),
|
||||
# qui réutilise les routes existantes (element_rename, element_delete).
|
||||
"label": "Onglets", "icon": "🗂️", "tag": "div", "has_content": False, "special_render": "onglets",
|
||||
"w": 60, "h": 40, "default_content": "",
|
||||
"controls": [],
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user