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()
}
}