mod error; pub use error::ErrorPage; pub use actix_web::Result as ResultPage; use crate::base::action; use crate::builder_fn; use crate::core::component::{Child, ChildOp, Component}; use crate::core::theme::{ChildrenInRegions, ThemeRef, REGION_CONTENT}; use crate::html::{html, Markup, DOCTYPE}; use crate::html::{AssetsOp, Context}; use crate::html::{AttrClasses, ClassesOp}; use crate::html::{AttrId, AttrL10n}; use crate::locale::{CharacterDirection, L10n, LangId, LanguageIdentifier}; use crate::service::HttpRequest; /// Representa una página HTML completa lista para renderizar. /// /// Una instancia de `Page` se compone dinámicamente permitiendo establecer título, descripción, /// regiones donde disponer los componentes, atributos de `` y otros aspectos del contexto de /// renderizado. #[rustfmt::skip] pub struct Page { title : AttrL10n, description : AttrL10n, metadata : Vec<(&'static str, &'static str)>, properties : Vec<(&'static str, &'static str)>, context : Context, body_id : AttrId, body_classes: AttrClasses, regions : ChildrenInRegions, } impl Page { /// Crea una nueva instancia de página. /// /// Si se proporciona la solicitud HTTP, se guardará en el contexto de renderizado de la página /// para poder ser recuperada por los componentes si es necesario. #[rustfmt::skip] pub fn new(request: Option) -> Self { Page { title : AttrL10n::default(), description : AttrL10n::default(), metadata : Vec::default(), properties : Vec::default(), context : Context::new(request), body_id : AttrId::default(), body_classes: AttrClasses::default(), regions : ChildrenInRegions::default(), } } // Page BUILDER ******************************************************************************** /// Establece el título de la página como un valor traducible. #[builder_fn] pub fn with_title(mut self, title: L10n) -> Self { self.title.alter_value(title); self } /// Establece la descripción de la página como un valor traducible. #[builder_fn] pub fn with_description(mut self, description: L10n) -> Self { self.description.alter_value(description); self } /// Añade una entrada `` al ``. #[builder_fn] pub fn with_metadata(mut self, name: &'static str, content: &'static str) -> Self { self.metadata.push((name, content)); self } /// Añade una entrada `` al ``. #[builder_fn] pub fn with_property(mut self, property: &'static str, content: &'static str) -> Self { self.metadata.push((property, content)); self } /// Modifica la fuente de idioma de la página ([`Context::with_langid()`]). #[builder_fn] pub fn with_langid(mut self, language: &impl LangId) -> Self { self.context.alter_langid(language); self } /// Modifica el tema que se usará para renderizar la página ([`Context::with_theme()`]). #[builder_fn] pub fn with_theme(mut self, theme_name: &'static str) -> Self { self.context.alter_theme(theme_name); self } /// Modifica la composición para renderizar la página ([`Context::with_layout()`]). #[builder_fn] pub fn with_layout(mut self, layout_name: &'static str) -> Self { self.context.alter_layout(layout_name); self } /// Define los recursos de la página usando [`AssetsOp`]. #[builder_fn] pub fn with_assets(mut self, op: AssetsOp) -> Self { self.context.alter_assets(op); self } /// Establece el atributo `id` del elemento ``. #[builder_fn] pub fn with_body_id(mut self, id: impl AsRef) -> Self { self.body_id.alter_value(id); self } /// Modifica las clases CSS del elemento `` con una operación sobre [`AttrClasses`]. #[builder_fn] pub fn with_body_classes(mut self, op: ClassesOp, classes: impl AsRef) -> Self { self.body_classes.alter_value(op, classes); self } /// Añade un componente a la región de contenido por defecto. pub fn with_component(mut self, component: impl Component) -> Self { self.regions .alter_child_in_region(REGION_CONTENT, ChildOp::Add(Child::with(component))); self } /// Añade un componente en una región (`region_name`) de la página. pub fn with_component_in( mut self, region_name: &'static str, component: impl Component, ) -> Self { self.regions .alter_child_in_region(region_name, ChildOp::Add(Child::with(component))); self } /// Opera con [`ChildOp`] en una región (`region_name`) de la página. #[builder_fn] pub fn with_child_in_region(mut self, region_name: &'static str, op: ChildOp) -> Self { self.regions.alter_child_in_region(region_name, op); self } // Page GETTERS ******************************************************************************** /// Devuelve el título traducido para el idioma de la página, si existe. pub fn title(&mut self) -> Option { self.title.lookup(&self.context) } /// Devuelve la descripción traducida para el idioma de la página, si existe. pub fn description(&mut self) -> Option { self.description.lookup(&self.context) } /// Devuelve la lista de metadatos ``. pub fn metadata(&self) -> &Vec<(&str, &str)> { &self.metadata } /// Devuelve la lista de propiedades ``. pub fn properties(&self) -> &Vec<(&str, &str)> { &self.properties } /// Devuelve la solicitud HTTP asociada. pub fn request(&self) -> Option<&HttpRequest> { self.context.request() } /// Devuelve el tema que se usará para renderizar la página. pub fn theme(&self) -> ThemeRef { self.context.theme() } /// Devuelve la composición para renderizar la página. Por defecto es `"default"`. pub fn layout(&self) -> &str { self.context.layout() } /// Devuelve el identificador del elemento ``. pub fn body_id(&self) -> &AttrId { &self.body_id } /// Devuelve las clases CSS del elemento ``. pub fn body_classes(&self) -> &AttrClasses { &self.body_classes } // Page RENDER ********************************************************************************* /// Renderiza los componentes de una región (`regiona_name`) de la página. pub fn render_region(&mut self, region_name: &'static str) -> Markup { self.regions .merge_all_components(self.context.theme(), region_name) .render(&mut self.context) } /// Renderiza los recursos de la página. pub fn render_assets(&self) -> Markup { self.context.render_assets() } /// Renderiza la página completa en formato HTML. /// /// Ejecuta las acciones correspondientes antes y después de renderizar el ``, /// así como del ``, e inserta los atributos `lang` y `dir` en la etiqueta ``. pub fn render(&mut self) -> ResultPage { // Acciones específicas del tema antes de renderizar el . self.context.theme().before_render_page_body(self); // Acciones de las extensiones antes de renderizar el . action::page::BeforeRenderBody::dispatch(self); // Renderiza el . let body = self.context.theme().render_page_body(self); // Acciones específicas del tema después de renderizar el . self.context.theme().after_render_page_body(self); // Acciones de las extensiones después de renderizar el . action::page::AfterRenderBody::dispatch(self); // Renderiza el . let head = self.context.theme().render_page_head(self); // Compone la página incluyendo los atributos de idioma y dirección del texto. let lang = &self.context.langid().language; let dir = match self.context.langid().character_direction() { CharacterDirection::LTR => "ltr", CharacterDirection::RTL => "rtl", CharacterDirection::TTB => "auto", }; Ok(html! { (DOCTYPE) html lang=(lang) dir=(dir) { (head) (body) } }) } } impl LangId for Page { fn langid(&self) -> &'static LanguageIdentifier { self.context.langid() } }