✨ (theme): Añade componentes Region y Template
Introduce Region y Template (`base::component::layout`) para componer el `<body>` de una página, con captura explícita vía `handle_component()` en lugar de maquetado implícito: - `RegionName`/`TemplateName` (`core/theme.rs`) como interfaces de identidad; `CoreRegion` (`Header`/`Content`/`Footer`) y `CoreTemplate` (`Standard`/`Admin`) como catálogo por defecto. - `ReservedRegion` (`PageTop`/`PageBottom`, en `response/page.rs`) queda fuera de `CoreRegion`. `Page::render()` las renderiza siempre, envolviendo `Theme::render_page_body()`, con independencia de la plantilla o el tema activos. - Elimina `TemplateSource` en `Context`: la plantilla pasa a ser un `TemplateRef` fijo, sin resolución por tema (ese mecanismo nunca llegó a implementarse). - La personalización por tema es por captura (`handle_component()` + `downcast_ref()`), no por sustituir qué constante se resuelve. - Corrige referencias obsoletas a `DefaultRegions` por `CoreRegion`. - Sustituye `tests/theme_template.rs` (API anterior) por `tests/component_template.rs`, que cubre el patrón de captura con el API nuevo.
This commit is contained in:
parent
e7f2563967
commit
8573aca29e
13 changed files with 560 additions and 391 deletions
|
|
@ -1,5 +1,7 @@
|
|||
//! Componentes nativos proporcionados por PageTop.
|
||||
|
||||
pub mod layout;
|
||||
|
||||
mod block;
|
||||
pub use block::Block;
|
||||
|
||||
|
|
|
|||
7
src/base/component/layout.rs
Normal file
7
src/base/component/layout.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
//! Definiciones para la composición de documentos ([`Region`] y [`Template`]).
|
||||
|
||||
mod region;
|
||||
pub use region::Region;
|
||||
|
||||
mod template;
|
||||
pub use template::Template;
|
||||
111
src/base/component/layout/region.rs
Normal file
111
src/base/component/layout/region.rs
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
/// Componente que renderiza una región del `<body>`.
|
||||
///
|
||||
/// No recibe ningún contenido de quien lo construye. Lo obtiene directamente del [`Context`] en el
|
||||
/// momento de renderizarse (ver [`Context::render_region()`]). Si la región no tiene contenido, no
|
||||
/// se renderiza nada.
|
||||
///
|
||||
/// Si un tema necesita maquetar una región determinada de forma distinta, puede capturar este
|
||||
/// componente en [`Theme::handle_component()`](crate::core::theme::Theme::handle_component) y hacer
|
||||
/// [`downcast_ref()`](crate::core::AnyCast::downcast_ref) sobre el [`RegionRef`] que devuelve
|
||||
/// [`Self::region()`], para compararlo con la variante deseada.
|
||||
///
|
||||
/// Como cualquier otro componente, participa también en el despacho de las
|
||||
/// [acciones de componentes](crate::base::action::component) para que otras extensiones puedan
|
||||
/// intervenir en su renderizado.
|
||||
///
|
||||
/// # Ejemplo
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use pagetop::prelude::*;
|
||||
///
|
||||
/// struct Sidebar;
|
||||
///
|
||||
/// impl RegionName for Sidebar {
|
||||
/// fn name(&self) -> &'static str {
|
||||
/// "sidebar"
|
||||
/// }
|
||||
///
|
||||
/// fn label(&self) -> L10n {
|
||||
/// L10n::n("Sidebar")
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let header = layout::Region::header();
|
||||
/// let sidebar = layout::Region::of(&Sidebar);
|
||||
/// ```
|
||||
#[derive(Clone, Getters)]
|
||||
pub struct Region {
|
||||
/// Devuelve la región subyacente.
|
||||
#[getters(copy)]
|
||||
region: RegionRef,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Region {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Region")
|
||||
.field("region", &self.region().name())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Region {
|
||||
fn default() -> Self {
|
||||
Region {
|
||||
region: &CoreRegion::Content,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Component for Region {
|
||||
fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Devuelve el nombre de la región subyacente como identificador del componente.
|
||||
fn id(&self) -> Option<String> {
|
||||
Some(self.region().name().to_owned())
|
||||
}
|
||||
|
||||
async fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||
let name = self.region().name();
|
||||
let content = cx.render_region(self.region()).await;
|
||||
Ok(html! {
|
||||
@if !content.is_empty() {
|
||||
div
|
||||
id=[self.id()]
|
||||
class=(util::join!("region region-", name))
|
||||
role="region"
|
||||
aria-label=[self.region().label().lookup(cx)]
|
||||
{
|
||||
(content)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Region {
|
||||
/// Define el componente que renderizará [`CoreRegion::Header`].
|
||||
pub fn header() -> Self {
|
||||
Region {
|
||||
region: &CoreRegion::Header,
|
||||
}
|
||||
}
|
||||
|
||||
/// Define el componente que renderizará [`CoreRegion::Footer`].
|
||||
pub fn footer() -> Self {
|
||||
Region {
|
||||
region: &CoreRegion::Footer,
|
||||
}
|
||||
}
|
||||
|
||||
/// Define el componente que renderizará la región indicada.
|
||||
pub fn of(region: RegionRef) -> Self {
|
||||
Region { region }
|
||||
}
|
||||
}
|
||||
81
src/base/component/layout/template.rs
Normal file
81
src/base/component/layout/template.rs
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
/// Componente que renderiza el cuerpo de una plantilla de regiones.
|
||||
///
|
||||
/// La composición por defecto usa el componente [`Region`](crate::base::component::layout::Region)
|
||||
/// para mostrar, en este orden, las regiones [`CoreRegion::Header`], [`CoreRegion::Content`] y
|
||||
/// [`CoreRegion::Footer`].
|
||||
///
|
||||
/// No incluye las regiones reservadas
|
||||
/// [`ReservedRegion::PageTop`](crate::response::ReservedRegion::PageTop) y
|
||||
/// [`ReservedRegion::PageBottom`](crate::response::ReservedRegion::PageBottom) porque el propio
|
||||
/// [`Page::render()`](crate::response::Page::render) las añade antes y después del resultado de
|
||||
/// [`Theme::render_page_body()`](crate::core::theme::Theme::render_page_body) para que se
|
||||
/// rendericen siempre, independientemente de la plantilla que se use.
|
||||
///
|
||||
/// Si un tema necesita maquetar una plantilla determinada de forma distinta, puede capturar este
|
||||
/// componente en [`Theme::handle_component()`](crate::core::theme::Theme::handle_component) y hacer
|
||||
/// [`downcast_ref()`](crate::core::AnyCast::downcast_ref) sobre el [`TemplateRef`] que devuelve
|
||||
/// [`Self::template()`], para compararlo con la variante deseada.
|
||||
///
|
||||
/// Como cualquier otro componente, participa también en el despacho de las
|
||||
/// [acciones de componentes](crate::base::action::component) para que otras extensiones puedan
|
||||
/// intervenir en su renderizado.
|
||||
#[derive(Clone, Getters)]
|
||||
pub struct Template {
|
||||
/// Devuelve la plantilla subyacente.
|
||||
#[getters(copy)]
|
||||
template: TemplateRef,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Template {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Template")
|
||||
.field("template", &self.template().name())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Template {
|
||||
fn default() -> Self {
|
||||
Template {
|
||||
template: &CoreTemplate::Standard,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Component for Template {
|
||||
fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Devuelve el nombre de la plantilla subyacente como identificador del componente.
|
||||
fn id(&self) -> Option<String> {
|
||||
Some(self.template().name().to_owned())
|
||||
}
|
||||
|
||||
async fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||
Ok(html! {
|
||||
(layout::Region::header().render(cx).await)
|
||||
(layout::Region::default().render(cx).await)
|
||||
(layout::Region::footer().render(cx).await)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Template {
|
||||
/// Define el componente que renderizará [`CoreTemplate::Admin`].
|
||||
pub fn admin() -> Self {
|
||||
Template {
|
||||
template: &CoreTemplate::Admin,
|
||||
}
|
||||
}
|
||||
|
||||
/// Define el componente que renderizará la plantilla indicada.
|
||||
pub fn of(template: TemplateRef) -> Self {
|
||||
Template { template }
|
||||
}
|
||||
}
|
||||
|
|
@ -25,7 +25,7 @@ impl Theme for Basic {
|
|||
.with_weight(-99),
|
||||
))
|
||||
.alter_child_in(
|
||||
&DefaultRegions::Footer,
|
||||
&CoreRegion::Footer,
|
||||
ChildOp::AddIfEmpty(PoweredBy::new().into()),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue