"""Catalogue des types d'éléments du support de formation (voir docs/plan/PLAN.md §1.2/§3.2) — bibliothèque groupée par catégorie visuelle (panneau gauche de l'éditeur), libellés d'affichage, et attributs par défaut posés à la création de chaque type.""" from typing import Any from ..rendering.box_style import BOX_DEFAULTS, default_border from .association_config import DEFAULT_ASSOCIATION_CONFIG from .memory_config import DEFAULT_MEMORY_CONFIG from .mots_config import DEFAULT_MOTS_CONFIG from .quiz_config import DEFAULT_QUIZ_CONFIG from .scenario_config import DEFAULT_SCENARIO_CONFIG CONTENT_KINDS = ("titre", "paragraphe", "image", "bouton", "liste_puces", "liste_numerotee", "badge", "carte") MINIGAME_KINDS = ("quiz", "association", "memory", "mots", "scenario", "zones") # "row" n'apparaît jamais dans la bibliothèque (créé implicitement par le # moteur de layout au dépôt d'un élément à côté d'un autre) — absent de # ELEMENT_LIBRARY, présent dans ELEMENT_KIND_LABELS pour l'affichage/debug. ELEMENT_LIBRARY: dict[str, dict[str, Any]] = { "contenu": {"label": "Contenu", "kinds": list(CONTENT_KINDS)}, "minigames": {"label": "Mini-jeux", "kinds": list(MINIGAME_KINDS)}, } ELEMENT_KIND_LABELS: dict[str, str] = { "row": "Rangée", "titre": "Titre", "paragraphe": "Paragraphe", "image": "Image", "bouton": "Bouton", "liste_puces": "Liste à puces", "liste_numerotee": "Liste numérotée", "badge": "Étiquette", "carte": "Carte", "quiz": "Quiz", "association": "Association", "memory": "Memory", "mots": "Mots mêlés", "scenario": "Scénario", "zones": "Zones à risque", } _TEXT_DEFAULTS = { "bold": False, "italic": False, "underline": False, "strikethrough": False, "align": "left", "color": "var(--forge-text)", # Vides par défaut = valeurs du préréglage `style` inchangées (voir # _STYLE_PRESETS dans render_document_element.py) — une valeur # explicite les remplace (retour utilisateur du 26/09/2026, audit des # réglages manquants). "font_family": "", "font_size": "", "line_height": "", "letter_spacing": "", "text_transform": "none", "text_shadow": "", # Attributs de boîte partagés avec d'autres kinds (dont `max_width`, # ex. "60ch"/"480px" — retour utilisateur du 24/09/2026 : un # paragraphe doit pouvoir rester plus étroit que la page) — voir # rendering/box_style.py. **BOX_DEFAULTS, } def element_default_attributes(kind: str) -> dict[str, Any]: """Attributs posés à la création d'un élément de ce type — cf. docs/plan/PLAN.md §3.3/§3.4/§3.5 pour la liste des propriétés éditables par panneau, ici juste leur valeur de départ.""" if kind == "titre": # "border" copié à chaque appel (jamais un dict partagé/muté par # référence entre plusieurs éléments — même raison que la copie # de "questions" pour le quiz plus bas). return {"content": "Nouveau titre", "style": "titre1", **_TEXT_DEFAULTS, "border": default_border()} if kind == "paragraphe": return { "content": "Nouveau paragraphe de texte.", "style": "paragraphe", **_TEXT_DEFAULTS, "border": default_border(), } if kind == "image": # svg_markup (optionnel) prend le pas sur src au rendu (voir # render_document_element._render_image) — un contenu vectoriel # dessiné/collé directement plutôt qu'un fichier hébergé. # aspect_ratio/filter_preset : vides par défaut (aucun style # ajouté). click_behavior/link_url/lazy_load/caption : # comportement/contenu, pas du style (retour utilisateur du # 26/09/2026, audit des réglages manquants — colonne "Image"). # click_behavior ("" | "link" | "lightbox") et link_url sont # mutuellement dépendants (un lien sans URL ne fait rien au # rendu, voir _render_image) mais jamais revalidés l'un par # rapport à l'autre ici : cette combinaison reste sans risque # quelle qu'elle soit. # # object_fit="cover" + height="220px" (au lieu de vides) : cadre # FIXE par défaut (retour utilisateur du 26/09/2026 : "ce cadre # ne devrait pas changer de taille en fonction de la taille de # l'image mais être fixe et contraindre l'image dedans") — une # photo importée est désormais TOUJOURS rognée pour remplir ce # cadre, quelle que soit sa résolution native, plutôt que de # dicter elle-même la taille du bloc. "Taille réelle" reste # sélectionnable explicitement dans le panneau Propriétés # (segmented "Ajustement dans son cadre") pour qui préfère # revenir à l'ancien comportement (hauteur libre, aucun # object-fit) ; un défaut CSS aveugle sur TOUTE image aurait # rendu ce choix impossible à distinguer de "jamais réglé", les # deux valant la chaîne vide. return { "src": "", "alt": "", "svg_markup": "", "object_fit": "cover", "aspect_ratio": "", "filter_preset": "", "click_behavior": "", "link_url": "", "lazy_load": False, "caption": "", **BOX_DEFAULTS, "height": "220px", "border": default_border(), } if kind == "bouton": # attachment_stored_name/attachment_filename (optionnels) : un # fichier téléchargeable joint au bouton (voir routes/document/ # document_element_upload_attachment.py), indépendant de `target` # qui reste réservé à la navigation (URL/ancre). Jamais les deux # à la fois côté UI (voir document-editor.js), mais rien ne # l'empêche structurellement ici. # # Audit du 26/09/2026 (réglages manquants — colonne "Bouton") : # bold/italic/text_transform/font_family/font_size/letter_spacing/ # text_color (typo, jamais gérés par box_style.py) + svg_markup/ # icon_position/icon_size (icône avant/après le texte, absente # même de l'étiquette qui n'a qu'un SVG fixe sans position ni # taille réglables) + les attributs de boîte partagés. bold=False # PAR DÉFAUT (le CSS de base garde son font-weight:700 tel quel # tant que "bold" n'est pas explicitement activé — voir # _render_button, qui ne pousse à 800 QUE si bold=True — aucune # régression visuelle sur les boutons déjà créés). return { "label": "Bouton", "target": "", "attachment_stored_name": "", "attachment_filename": "", "bold": False, "italic": False, "text_transform": "none", "font_family": "", "font_size": "", "letter_spacing": "", "text_color": "", "svg_markup": "", "icon_position": "before", "icon_size": "", **BOX_DEFAULTS, "border": default_border(), } if kind in ("liste_puces", "liste_numerotee"): # Une seule et même structure d'attributs pour les deux kinds — # "ordonnée ou non" se lit directement sur le kind au moment du # rendu (voir render_document_element._render_list), jamais un # attribut "ordered" redondant à tenir synchronisé avec le kind. # # Audit du 26/09/2026 (réglages manquants — colonne "Liste à # puces/numérotée") : portée actée avec l'utilisateur = style de # la LISTE ENTIÈRE (typo/puces/boîte/bordure/fond), jamais un # style par élément individuel ni de sous-listes (chantier bien # plus lourd, transformerait `items` d'une liste de chaînes en # objets structurés — différé à une demande séparée). Seule # exception : `item_padding`, un padding UNIFORME appliqué à # CHAQUE élément (retour utilisateur explicite : "il faut un # padding de base par élément de liste car y en a pas # aujourd'hui") — une valeur PARTAGÉE par tous les éléments, # jamais réglable individuellement, voir _render_list. # list_style_type : valide uniquement parmi les valeurs propres # au kind (disc/circle/square/none pour puces, # decimal/.../none pour numérotée) — vérifié au rendu, jamais ici. # svg_markup (puce personnalisée) : ignoré au rendu pour # liste_numerotee (une puce imagée n'a pas de sens sur une liste # numérotée, voir _render_list). return { "items": ["Premier élément", "Deuxième élément"], "bold": False, "italic": False, "underline": False, "font_family": "", "font_size": "", "line_height": "", "text_color": "", "list_style_type": "", "list_style_position": "outside", "marker_color": "", "marker_size": "", "svg_markup": "", "item_padding": "6px", "item_spacing": "", **BOX_DEFAULTS, "border": default_border(), } if kind == "badge": # Même esprit que titre/paragraphe (bold/align/color... déjà des # attributs par élément, pas des choix figés par le moteur) : # svg_markup/width/border_radius/bold/uppercase vides ou False par # défaut = comportement historique inchangé (pleine largeur, sans # icône, casse normale) ; un thème ou le créateur les règle au cas # par cas (retour utilisateur du 24/09/2026). return { "content": "Étiquette", "svg_markup": "", "width": "", "border_radius": "", "bold": False, "uppercase": False, } if kind == "carte": # Contenu pur (lettre/repère court + titre + description) — sans # aucun choix de couleur/forme, laissé au futur système de # templates (voir consigne du 24/09/2026 : moteur = contenu et # mécanisme uniquement, jamais de style). return {"label": "A", "title": "Titre de la carte", "description": "Description de la carte."} if kind == "row": return {"gap": 16, "align": "stretch", "justify": "flex-start"} if kind == "quiz": # Copie de "questions" (liste, jamais un simple dict(...) qui la # partagerait par référence avec DEFAULT_QUIZ_CONFIG) : aucun appelant # ne la mute en place aujourd'hui, mais la copier ici coûte rien et # évite d'ancrer cette hypothèse fragile pour la suite. return {**DEFAULT_QUIZ_CONFIG, "questions": list(DEFAULT_QUIZ_CONFIG["questions"])} if kind == "association": # Même raison de copie que "quiz" ci-dessus (voir association_config.py). return {**DEFAULT_ASSOCIATION_CONFIG, "pairs": list(DEFAULT_ASSOCIATION_CONFIG["pairs"])} if kind == "memory": # Même raison de copie que "quiz"/"association" ci-dessus (voir memory_config.py). return {**DEFAULT_MEMORY_CONFIG, "cards": list(DEFAULT_MEMORY_CONFIG["cards"])} if kind == "mots": # Même raison de copie que "quiz"/"association"/"memory" ci-dessus (voir mots_config.py). return {**DEFAULT_MOTS_CONFIG, "words": list(DEFAULT_MOTS_CONFIG["words"])} if kind == "scenario": # Même raison de copie que ci-dessus (voir scenario_config.py). return {**DEFAULT_SCENARIO_CONFIG, "scenarios": list(DEFAULT_SCENARIO_CONFIG["scenarios"])} if kind in MINIGAME_KINDS: # Panneau Propriétés minimal (voir PLAN.md §3.5, dernier # paragraphe : "état par défaut en attendant sa spécification") — # décision actée : cœur complet + mini-jeux en emplacement # réservé (mots/scenario/zones), formulaires de contenu dédiés = # chantier séparé. return {"theme_color": "#ff5f2e"} return {}