♻️ Refactoriza la gestión de regiones y plantillas

This commit is contained in:
Manuel Cillero 2025-11-30 00:16:54 +01:00
parent bfdc0da407
commit f2733bb250
15 changed files with 494 additions and 655 deletions

View file

@ -1,46 +1,8 @@
//! Componentes nativos proporcionados por PageTop.
//!
//! Conviene destacar que PageTop distingue entre:
//!
//! - **Componentes estructurales** que definen el esqueleto de un documento HTML, como [`Template`]
//! y [`Region`], utilizados por [`Page`](crate::response::page::Page) para generar la estructura
//! final.
//! - **Componentes de contenido** (menús, barras, tarjetas, etc.), que se incluyen en las regiones
//! gestionadas por los componentes estructurales.
//!
//! El componente [`Template`] describe cómo maquetar el cuerpo del documento a partir de varias
//! regiones lógicas ([`Region`]). En función de la plantilla seleccionada, determina qué regiones
//! se renderizan y en qué orden. Por ejemplo, la plantilla predeterminada [`Template::DEFAULT`]
//! utiliza las regiones [`Region::HEADER`], [`Region::CONTENT`] y [`Region::FOOTER`].
//!
//! Un componente [`Region`] es un contenedor lógico asociado a un nombre de región. Su contenido se
//! obtiene del [`Context`](crate::core::component::Context), donde los componentes se registran
//! mediante [`Contextual::with_child_in()`](crate::core::component::Contextual::with_child_in) y
//! otros mecanismos similares, y se integra en el documento a través de [`Template`].
//!
//! Por su parte, una página ([`Page`](crate::response::page::Page)) representa un documento HTML
//! completo. Implementa [`Contextual`](crate::core::component::Contextual) para mantener su propio
//! [`Context`](crate::core::component::Context), donde gestiona el tema activo, la plantilla
//! seleccionada y los componentes asociados a cada región, y se encarga de generar la estructura
//! final de la página.
//!
//! De este modo, temas y extensiones colaboran sobre una estructura común: las aplicaciones
//! registran componentes en el [`Context`](crate::core::component::Context), las plantillas
//! organizan las regiones y las páginas generan el documento HTML resultante.
//!
//! Los temas pueden sobrescribir [`Template`] para exponer nuevas plantillas o adaptar las
//! predeterminadas, y lo mismo con [`Region`] para añadir regiones adicionales o personalizar su
//! representación.
mod html;
pub use html::Html;
mod region;
pub use region::Region;
mod template;
pub use template::Template;
mod block;
pub use block::Block;

View file

@ -1,150 +0,0 @@
use crate::prelude::*;
/// Componente estructural que renderiza el contenido de una región del documento.
///
/// `Region` actúa como un contenedor lógico asociado a un nombre de región. Su contenido se obtiene
/// del contexto de renderizado ([`Context`]), donde los componentes suelen registrarse con métodos
/// como [`Contextual::with_child_in()`]. Cada región puede integrarse posteriormente en el cuerpo
/// del documento mediante [`Template`], normalmente desde una página ([`Page`]).
#[derive(AutoDefault)]
pub struct Region {
#[default(AttrName::new(Self::DEFAULT))]
name: AttrName,
#[default(L10n::l("region-content"))]
label: L10n,
}
impl Component for Region {
fn new() -> Self {
Region::default()
}
fn id(&self) -> Option<String> {
self.name.get()
}
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
let Some(name) = self.name().get() else {
return PrepareMarkup::None;
};
let output = cx.render_region(&name);
if output.is_empty() {
return PrepareMarkup::None;
}
PrepareMarkup::With(html! {
div
id=[self.id()]
class=(join!("region region-", &name))
role="region"
aria-label=[self.label().lookup(cx)]
{
(output)
}
})
}
}
impl Region {
/// Región especial situada al **inicio del documento**.
///
/// Su función es proporcionar un punto estable donde las extensiones puedan inyectar contenido
/// global antes de renderizar el resto de regiones principales (cabecera, contenido, etc.).
///
/// No suele utilizarse en los temas como una región “visible” dentro del maquetado habitual,
/// sino como punto de anclaje para elementos auxiliares, marcadores técnicos, inicializadores o
/// contenido de depuración que deban situarse en la parte superior del documento.
///
/// Se considera una región **reservada** para este tipo de usos globales.
pub const PAGETOP: &str = "page-top";
/// Región estándar para la **cabecera** del documento.
///
/// Suele emplearse para mostrar un logotipo, navegación principal, barras superiores, etc.
pub const HEADER: &str = "header";
/// Región principal de **contenido**.
///
/// Es la región donde se espera que se renderice el contenido principal de la página (p. ej.
/// cuerpo de la ruta actual, bloques centrales, vistas principales, etc.). En muchos temas será
/// la región mínima imprescindible para que la página tenga sentido.
pub const CONTENT: &str = "content";
/// Región estándar para el **pie de página**.
///
/// Suele contener información legal, enlaces secundarios, créditos, etc.
pub const FOOTER: &str = "footer";
/// Región especial situada al **final del documento**.
///
/// Pensada para proporcionar un punto estable donde las extensiones puedan inyectar contenido
/// global después de renderizar el resto de regiones principales (cabecera, contenido, etc.).
///
/// No suele utilizarse en los temas como una región “visible” dentro del maquetado habitual,
/// sino como punto de anclaje para elementos auxiliares asociados a comportamientos dinámicos
/// que deban situarse en la parte inferior del documento.
///
/// Igual que [`Self::PAGETOP`], se considera una región **reservada** para este tipo de usos
/// globales.
pub const PAGEBOTTOM: &str = "page-bottom";
/// Región por defecto que se asigna cuando no se especifica ningún nombre.
///
/// Por diseño, la región por defecto es la de contenido principal ([`Self::CONTENT`]), de
/// manera que un tema sencillo pueda limitarse a definir una sola región funcional.
pub const DEFAULT: &str = Self::CONTENT;
/// Prepara una región para el nombre indicado.
///
/// El valor de `name` se utiliza como nombre de la región y como identificador (`id`) del
/// contenedor. Al renderizarse, este componente mostrará el contenido registrado en el contexto
/// bajo ese nombre.
pub fn named(name: impl AsRef<str>) -> Self {
Region {
name: AttrName::new(name),
label: L10n::default(),
}
}
/// Prepara una región para el nombre indicado con una etiqueta de accesibilidad.
///
/// El valor de `name` se utiliza como nombre de la región y como identificador (`id`) del
/// contenedor, mientras que `label` será el texto localizado que se usará como `aria-label` del
/// contenedor.
pub fn labeled(name: impl AsRef<str>, label: L10n) -> Self {
Region {
name: AttrName::new(name),
label,
}
}
// **< Region BUILDER >*************************************************************************
/// Establece o modifica el nombre de la región.
#[builder_fn]
pub fn with_name(mut self, name: impl AsRef<str>) -> Self {
self.name.alter_value(name);
self
}
/// Establece la etiqueta localizada de la región.
///
/// Esta etiqueta se utiliza como `aria-label` del contenedor predefinido `<div role="region">`,
/// lo que mejora la accesibilidad para lectores de pantalla y otras tecnologías de apoyo.
#[builder_fn]
pub fn with_label(mut self, label: L10n) -> Self {
self.label = label;
self
}
// **< Region GETTERS >*************************************************************************
/// Devuelve el nombre de la región.
pub fn name(&self) -> &AttrName {
&self.name
}
/// Devuelve la etiqueta localizada asociada a la región.
pub fn label(&self) -> &L10n {
&self.label
}
}

View file

@ -1,84 +0,0 @@
use crate::prelude::*;
/// Componente estructural para renderizar plantillas de contenido.
///
/// `Template` describe cómo se compone el cuerpo del documento a partir de varias regiones lógicas
/// ([`Region`]). En función de su nombre, decide qué regiones se renderizan y en qué orden.
///
/// Normalmente se invoca desde una página ([`Page`]), que consulta el nombre de plantilla guardado
/// en el [`Context`] y delega en `Template` la composición de las regiones que forman el cuerpo del
/// documento.
///
/// Los temas pueden sobrescribir este componente para exponer sus propias plantillas o adaptar las
/// plantillas predeterminadas.
#[derive(AutoDefault)]
pub struct Template {
#[default(AttrName::new(Self::DEFAULT))]
name: AttrName,
}
impl Component for Template {
fn new() -> Self {
Template::default()
}
fn id(&self) -> Option<String> {
self.name.get()
}
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
let Some(name) = self.name().get() else {
return PrepareMarkup::None;
};
match name.as_str() {
Self::DEFAULT | Self::ERROR => PrepareMarkup::With(html! {
(Region::labeled(Region::HEADER, L10n::l("region-header")).render(cx))
(Region::default().render(cx))
(Region::labeled(Region::FOOTER, L10n::l("region-footer")).render(cx))
}),
_ => PrepareMarkup::None,
}
}
}
impl Template {
/// Nombre de la plantilla predeterminada.
///
/// Por defecto define una estructura básica con las regiones [`Region::HEADER`],
/// [`Region::CONTENT`] y [`Region::FOOTER`], en ese orden. Esta plantilla se usa cuando no se
/// selecciona ninguna otra de forma explícita (ver [`Contextual::with_template()`]).
pub const DEFAULT: &str = "default";
/// Nombre de la plantilla de error.
///
/// Se utiliza para páginas de error u otros estados excepcionales. Por defecto reutiliza
/// la misma estructura que [`Self::DEFAULT`], pero permite a temas y extensiones distinguir
/// el contexto de error para aplicar estilos o contenidos específicos.
pub const ERROR: &str = "error";
/// Selecciona la plantilla asociada al nombre indicado.
///
/// El valor de `name` se utiliza como nombre de la plantilla y como identificador (`id`) del
/// componente.
pub fn named(name: impl AsRef<str>) -> Self {
Template {
name: AttrName::new(name),
}
}
// **< Template BUILDER >***********************************************************************
/// Establece o modifica el nombre de la plantilla seleccionada.
#[builder_fn]
pub fn with_name(mut self, name: impl AsRef<str>) -> Self {
self.name.alter_value(name);
self
}
// **< Template GETTERS >***********************************************************************
/// Devuelve el nombre de la plantilla seleccionada.
pub fn name(&self) -> &AttrName {
&self.name
}
}

View file

@ -23,7 +23,7 @@ impl Theme for Basic {
.with_weight(-99),
))
.alter_child_in(
Region::FOOTER,
&DefaultRegion::Footer,
ChildOp::AddIfEmpty(Child::with(PoweredBy::new())),
);
}