🎨 Mejora la página de bienvenida y el tema básico #6
8 changed files with 356 additions and 260 deletions
|
@ -48,14 +48,13 @@ pub struct App {
|
||||||
pub description: String,
|
pub description: String,
|
||||||
/// Tema predeterminado.
|
/// Tema predeterminado.
|
||||||
pub theme: String,
|
pub theme: String,
|
||||||
/// Idioma por defecto para la aplicación.
|
/// Idioma por defecto de la aplicación.
|
||||||
///
|
///
|
||||||
/// Si no se especifica un valor válido, normalmente se usará el idioma devuelto por la
|
/// Si este valor no es válido, el idioma efectivo para el renderizado se resolverá mediante la
|
||||||
/// implementación de [`LangId`](crate::locale::LangId) para [`Context`](crate::html::Context),
|
/// implementación de [`LangId`](crate::locale::LangId) en este orden: primero, el establecido
|
||||||
/// en el siguiente orden: primero, el idioma establecido explícitamente con
|
/// explícitamente con [`Contextual::with_langid()`](crate::html::Contextual::with_langid); si
|
||||||
/// [`Context::with_langid()`](crate::html::Context::with_langid); si no se ha definido, se
|
/// no se ha definido, se usará el indicado en la cabecera `Accept-Language` del navegador; y,
|
||||||
/// usará el indicado en la cabecera `Accept-Language` del navegador; y, si ninguno aplica, se
|
/// si ninguno aplica, se empleará el idioma de respaldo ("en-US").
|
||||||
/// empleará el idioma de respaldo ("en-US").
|
|
||||||
pub language: String,
|
pub language: String,
|
||||||
/// Banner ASCII mostrado al inicio: *"Off"* (desactivado), *"Slant"*, *"Small"*, *"Speed"* o
|
/// Banner ASCII mostrado al inicio: *"Off"* (desactivado), *"Slant"*, *"Small"*, *"Speed"* o
|
||||||
/// *"Starwars"*.
|
/// *"Starwars"*.
|
||||||
|
|
|
@ -9,12 +9,12 @@ mod assets;
|
||||||
pub use assets::favicon::Favicon;
|
pub use assets::favicon::Favicon;
|
||||||
pub use assets::javascript::JavaScript;
|
pub use assets::javascript::JavaScript;
|
||||||
pub use assets::stylesheet::{StyleSheet, TargetMedia};
|
pub use assets::stylesheet::{StyleSheet, TargetMedia};
|
||||||
pub(crate) use assets::Assets;
|
pub use assets::{Asset, Assets};
|
||||||
|
|
||||||
// HTML DOCUMENT CONTEXT ***************************************************************************
|
// HTML DOCUMENT CONTEXT ***************************************************************************
|
||||||
|
|
||||||
mod context;
|
mod context;
|
||||||
pub use context::{AssetsOp, Context, ErrorParam};
|
pub use context::{AssetsOp, Context, Contextual, ErrorParam};
|
||||||
|
|
||||||
// HTML ATTRIBUTES *********************************************************************************
|
// HTML ATTRIBUTES *********************************************************************************
|
||||||
|
|
||||||
|
|
|
@ -5,20 +5,20 @@ pub mod stylesheet;
|
||||||
use crate::html::{html, Markup, Render};
|
use crate::html::{html, Markup, Render};
|
||||||
use crate::{AutoDefault, Weight};
|
use crate::{AutoDefault, Weight};
|
||||||
|
|
||||||
pub trait AssetsTrait: Render {
|
pub trait Asset: Render {
|
||||||
// Devuelve el nombre del recurso, utilizado como clave única.
|
/// Devuelve el nombre del recurso, utilizado como clave única.
|
||||||
fn name(&self) -> &str;
|
fn name(&self) -> &str;
|
||||||
|
|
||||||
// Devuelve el peso del recurso, durante el renderizado se procesan de menor a mayor peso.
|
/// Devuelve el peso del recurso, durante el renderizado se procesan de menor a mayor peso.
|
||||||
fn weight(&self) -> Weight;
|
fn weight(&self) -> Weight;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(AutoDefault)]
|
#[derive(AutoDefault)]
|
||||||
pub(crate) struct Assets<T>(Vec<T>);
|
pub struct Assets<T>(Vec<T>);
|
||||||
|
|
||||||
impl<T: AssetsTrait> Assets<T> {
|
impl<T: Asset> Assets<T> {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Assets::<T>(Vec::<T>::new())
|
Self(Vec::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add(&mut self, asset: T) -> bool {
|
pub fn add(&mut self, asset: T) -> bool {
|
||||||
|
@ -49,14 +49,14 @@ impl<T: AssetsTrait> Assets<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: AssetsTrait> Render for Assets<T> {
|
impl<T: Asset> Render for Assets<T> {
|
||||||
fn render(&self) -> Markup {
|
fn render(&self) -> Markup {
|
||||||
let mut assets = self.0.iter().collect::<Vec<_>>();
|
let mut assets = self.0.iter().collect::<Vec<_>>();
|
||||||
assets.sort_by_key(|a| a.weight());
|
assets.sort_by_key(|a| a.weight());
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
@for a in assets {
|
@for a in assets {
|
||||||
(a.render())
|
(a)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::html::assets::AssetsTrait;
|
use crate::html::assets::Asset;
|
||||||
use crate::html::{html, Markup, Render};
|
use crate::html::{html, Markup, Render};
|
||||||
use crate::{join, join_pair, AutoDefault, Weight};
|
use crate::{join, join_pair, AutoDefault, Weight};
|
||||||
|
|
||||||
|
@ -137,7 +137,7 @@ impl JavaScript {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AssetsTrait for JavaScript {
|
impl Asset for JavaScript {
|
||||||
// Para *scripts* externos es la ruta; para *scripts* embebidos, un identificador.
|
// Para *scripts* externos es la ruta; para *scripts* embebidos, un identificador.
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
match &self.source {
|
match &self.source {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::html::assets::AssetsTrait;
|
use crate::html::assets::Asset;
|
||||||
use crate::html::{html, Markup, PreEscaped, Render};
|
use crate::html::{html, Markup, PreEscaped, Render};
|
||||||
use crate::{join_pair, AutoDefault, Weight};
|
use crate::{join_pair, AutoDefault, Weight};
|
||||||
|
|
||||||
|
@ -142,7 +142,7 @@ impl StyleSheet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AssetsTrait for StyleSheet {
|
impl Asset for StyleSheet {
|
||||||
// Para hojas de estilos externas es la ruta; para las embebidas, un identificador.
|
// Para hojas de estilos externas es la ruta; para las embebidas, un identificador.
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
match &self.source {
|
match &self.source {
|
||||||
|
|
|
@ -10,7 +10,7 @@ use crate::{builder_fn, join};
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
/// Operaciones para modificar el contexto ([`Context`]) del documento.
|
/// Operaciones para modificar el contexto ([`Context`]) de un documento.
|
||||||
pub enum AssetsOp {
|
pub enum AssetsOp {
|
||||||
// Favicon.
|
// Favicon.
|
||||||
/// Define el *favicon* del documento. Sobrescribe cualquier valor anterior.
|
/// Define el *favicon* del documento. Sobrescribe cualquier valor anterior.
|
||||||
|
@ -47,7 +47,64 @@ pub enum ErrorParam {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Representa el contexto de un documento HTML.
|
pub trait Contextual: LangId {
|
||||||
|
// Contextual BUILDER **************************************************************************
|
||||||
|
|
||||||
|
/// Asigna la fuente de idioma del documento.
|
||||||
|
#[builder_fn]
|
||||||
|
fn with_langid(self, language: &impl LangId) -> Self;
|
||||||
|
|
||||||
|
/// Asigna la solicitud HTTP al contexto.
|
||||||
|
#[builder_fn]
|
||||||
|
fn with_request(self, request: Option<HttpRequest>) -> Self;
|
||||||
|
|
||||||
|
/// Asigna el tema para renderizar el documento.
|
||||||
|
#[builder_fn]
|
||||||
|
fn with_theme(self, theme_name: &'static str) -> Self;
|
||||||
|
|
||||||
|
/// Asigna la composición para renderizar el documento.
|
||||||
|
#[builder_fn]
|
||||||
|
fn with_layout(self, layout_name: &'static str) -> Self;
|
||||||
|
|
||||||
|
/// Añade o modifica un parámetro dinámico del contexto.
|
||||||
|
#[builder_fn]
|
||||||
|
fn with_param<T: 'static>(self, key: &'static str, value: T) -> Self;
|
||||||
|
|
||||||
|
/// Define los recursos del contexto usando [`AssetsOp`].
|
||||||
|
#[builder_fn]
|
||||||
|
fn with_assets(self, op: AssetsOp) -> Self;
|
||||||
|
|
||||||
|
// Contextual GETTERS **************************************************************************
|
||||||
|
|
||||||
|
/// Devuelve una referencia a la solicitud HTTP asociada, si existe.
|
||||||
|
fn request(&self) -> Option<&HttpRequest>;
|
||||||
|
|
||||||
|
/// Devuelve el tema que se usará para renderizar el documento.
|
||||||
|
fn theme(&self) -> ThemeRef;
|
||||||
|
|
||||||
|
/// Devuelve la composición para renderizar el documento. Por defecto es `"default"`.
|
||||||
|
fn layout(&self) -> &str;
|
||||||
|
|
||||||
|
/// Recupera un parámetro como [`Option`], simplificando el acceso.
|
||||||
|
fn param<T: 'static>(&self, key: &'static str) -> Option<&T>;
|
||||||
|
|
||||||
|
/// Devuelve el Favicon de los recursos del contexto.
|
||||||
|
fn favicon(&self) -> Option<&Favicon>;
|
||||||
|
|
||||||
|
/// Devuelve las hojas de estilo de los recursos del contexto.
|
||||||
|
fn stylesheets(&self) -> &Assets<StyleSheet>;
|
||||||
|
|
||||||
|
/// Devuelve los scripts JavaScript de los recursos del contexto.
|
||||||
|
fn javascripts(&self) -> &Assets<JavaScript>;
|
||||||
|
|
||||||
|
// Contextual HELPERS **************************************************************************
|
||||||
|
|
||||||
|
/// Devuelve un identificador único dentro del contexto para el tipo `T`, si no se proporciona
|
||||||
|
/// un `id` explícito.
|
||||||
|
fn required_id<T>(&mut self, id: Option<String>) -> String;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implementa el contexto de un documento HTML.
|
||||||
///
|
///
|
||||||
/// Se crea internamente para manejar información relevante del documento, como la solicitud HTTP de
|
/// Se crea internamente para manejar información relevante del documento, como la solicitud HTTP de
|
||||||
/// origen, el idioma, tema y composición para el renderizado, los recursos *favicon* ([`Favicon`]),
|
/// origen, el idioma, tema y composición para el renderizado, los recursos *favicon* ([`Favicon`]),
|
||||||
|
@ -107,7 +164,7 @@ pub struct Context {
|
||||||
favicon : Option<Favicon>, // Favicon, si se ha definido.
|
favicon : Option<Favicon>, // Favicon, si se ha definido.
|
||||||
stylesheets: Assets<StyleSheet>, // Hojas de estilo CSS.
|
stylesheets: Assets<StyleSheet>, // Hojas de estilo CSS.
|
||||||
javascripts: Assets<JavaScript>, // Scripts JavaScript.
|
javascripts: Assets<JavaScript>, // Scripts JavaScript.
|
||||||
params : HashMap<&'static str, (Box<dyn Any>, &'static str)>, // Parámetros definidos en tiempo de ejecución.
|
params : HashMap<&'static str, (Box<dyn Any>, &'static str)>, // Parámetros en ejecución.
|
||||||
id_counter : usize, // Contador para generar identificadores únicos.
|
id_counter : usize, // Contador para generar identificadores únicos.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,80 +208,6 @@ impl Context {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Context BUILDER *****************************************************************************
|
|
||||||
|
|
||||||
/// Modifica la fuente de idioma del documento.
|
|
||||||
#[builder_fn]
|
|
||||||
pub fn with_langid(mut self, language: &impl LangId) -> Self {
|
|
||||||
self.langid = language.langid();
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Modifica el tema que se usará para renderizar el documento.
|
|
||||||
///
|
|
||||||
/// Localiza el tema por su [`short_name()`](crate::core::AnyInfo::short_name), y si no aplica
|
|
||||||
/// ninguno entonces usará el tema por defecto.
|
|
||||||
#[builder_fn]
|
|
||||||
pub fn with_theme(mut self, theme_name: &'static str) -> Self {
|
|
||||||
self.theme = theme_by_short_name(theme_name).unwrap_or(*DEFAULT_THEME);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Modifica la composición para renderizar el documento.
|
|
||||||
#[builder_fn]
|
|
||||||
pub fn with_layout(mut self, layout_name: &'static str) -> Self {
|
|
||||||
self.layout = layout_name;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Define los recursos del contexto usando [`AssetsOp`].
|
|
||||||
#[builder_fn]
|
|
||||||
pub fn with_assets(mut self, op: AssetsOp) -> Self {
|
|
||||||
match op {
|
|
||||||
// Favicon.
|
|
||||||
AssetsOp::SetFavicon(favicon) => {
|
|
||||||
self.favicon = favicon;
|
|
||||||
}
|
|
||||||
AssetsOp::SetFaviconIfNone(icon) => {
|
|
||||||
if self.favicon.is_none() {
|
|
||||||
self.favicon = Some(icon);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Stylesheets.
|
|
||||||
AssetsOp::AddStyleSheet(css) => {
|
|
||||||
self.stylesheets.add(css);
|
|
||||||
}
|
|
||||||
AssetsOp::RemoveStyleSheet(path) => {
|
|
||||||
self.stylesheets.remove(path);
|
|
||||||
}
|
|
||||||
// JavaScripts.
|
|
||||||
AssetsOp::AddJavaScript(js) => {
|
|
||||||
self.javascripts.add(js);
|
|
||||||
}
|
|
||||||
AssetsOp::RemoveJavaScript(path) => {
|
|
||||||
self.javascripts.remove(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
// Context GETTERS *****************************************************************************
|
|
||||||
|
|
||||||
/// Devuelve una referencia a la solicitud HTTP asociada, si existe.
|
|
||||||
pub fn request(&self) -> Option<&HttpRequest> {
|
|
||||||
self.request.as_ref()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Devuelve el tema que se usará para renderizar el documento.
|
|
||||||
pub fn theme(&self) -> ThemeRef {
|
|
||||||
self.theme
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Devuelve la composición para renderizar el documento. Por defecto es `"default"`.
|
|
||||||
pub fn layout(&self) -> &str {
|
|
||||||
self.layout
|
|
||||||
}
|
|
||||||
|
|
||||||
// Context RENDER ******************************************************************************
|
// Context RENDER ******************************************************************************
|
||||||
|
|
||||||
/// Renderiza los recursos del contexto.
|
/// Renderiza los recursos del contexto.
|
||||||
|
@ -240,61 +223,6 @@ impl Context {
|
||||||
|
|
||||||
// Context PARAMS ******************************************************************************
|
// Context PARAMS ******************************************************************************
|
||||||
|
|
||||||
/// Añade o modifica un parámetro dinámico del contexto.
|
|
||||||
///
|
|
||||||
/// El valor se guarda conservando el *nombre del tipo* real para mejorar los mensajes de error
|
|
||||||
/// posteriores.
|
|
||||||
///
|
|
||||||
/// # Ejemplos
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// use pagetop::prelude::*;
|
|
||||||
///
|
|
||||||
/// let cx = Context::new(None)
|
|
||||||
/// .with_param("usuario_id", 42_i32)
|
|
||||||
/// .with_param("titulo", String::from("Hola"))
|
|
||||||
/// .with_param("flags", vec!["a", "b"]);
|
|
||||||
/// ```
|
|
||||||
#[builder_fn]
|
|
||||||
pub fn with_param<T: 'static>(mut self, key: &'static str, value: T) -> Self {
|
|
||||||
let type_name = TypeInfo::FullName.of::<T>();
|
|
||||||
self.params.insert(key, (Box::new(value), type_name));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Recupera un parámetro como [`Option`], simplificando el acceso.
|
|
||||||
///
|
|
||||||
/// A diferencia de [`get_param`](Self::get_param), que devuelve un [`Result`] con información
|
|
||||||
/// detallada de error, este método devuelve `None` tanto si la clave no existe como si el valor
|
|
||||||
/// guardado no coincide con el tipo solicitado.
|
|
||||||
///
|
|
||||||
/// Resulta útil en escenarios donde sólo interesa saber si el valor existe y es del tipo
|
|
||||||
/// correcto, sin necesidad de diferenciar entre error de ausencia o de tipo.
|
|
||||||
///
|
|
||||||
/// # Ejemplo
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// use pagetop::prelude::*;
|
|
||||||
///
|
|
||||||
/// let cx = Context::new(None).with_param("username", String::from("Alice"));
|
|
||||||
///
|
|
||||||
/// // Devuelve Some(&String) si existe y coincide el tipo.
|
|
||||||
/// assert_eq!(cx.param::<String>("username").map(|s| s.as_str()), Some("Alice"));
|
|
||||||
///
|
|
||||||
/// // Devuelve None si no existe o si el tipo no coincide.
|
|
||||||
/// assert!(cx.param::<i32>("username").is_none());
|
|
||||||
/// assert!(cx.param::<String>("missing").is_none());
|
|
||||||
///
|
|
||||||
/// // Acceso con valor por defecto.
|
|
||||||
/// let user = cx.param::<String>("missing")
|
|
||||||
/// .cloned()
|
|
||||||
/// .unwrap_or_else(|| "visitor".to_string());
|
|
||||||
/// assert_eq!(user, "visitor");
|
|
||||||
/// ```
|
|
||||||
pub fn param<T: 'static>(&self, key: &'static str) -> Option<&T> {
|
|
||||||
self.get_param::<T>(key).ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Recupera una *referencia tipada* al parámetro solicitado.
|
/// Recupera una *referencia tipada* al parámetro solicitado.
|
||||||
///
|
///
|
||||||
/// Devuelve:
|
/// Devuelve:
|
||||||
|
@ -328,23 +256,6 @@ impl Context {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Elimina un parámetro del contexto. Devuelve `true` si la clave existía y se eliminó.
|
|
||||||
///
|
|
||||||
/// Devuelve `false` en caso contrario. Usar cuando solo interesa borrar la entrada.
|
|
||||||
///
|
|
||||||
/// # Ejemplos
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// use pagetop::prelude::*;
|
|
||||||
///
|
|
||||||
/// let mut cx = Context::new(None).with_param("temp", 1u8);
|
|
||||||
/// assert!(cx.remove_param("temp"));
|
|
||||||
/// assert!(!cx.remove_param("temp")); // ya no existe
|
|
||||||
/// ```
|
|
||||||
pub fn remove_param(&mut self, key: &'static str) -> bool {
|
|
||||||
self.params.remove(key).is_some()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Recupera el parámetro solicitado y lo elimina del contexto.
|
/// Recupera el parámetro solicitado y lo elimina del contexto.
|
||||||
///
|
///
|
||||||
/// Devuelve:
|
/// Devuelve:
|
||||||
|
@ -380,30 +291,21 @@ impl Context {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Context EXTRAS ******************************************************************************
|
/// Elimina un parámetro del contexto. Devuelve `true` si la clave existía y se eliminó.
|
||||||
|
|
||||||
/// Genera un identificador único si no se proporciona uno explícito.
|
|
||||||
///
|
///
|
||||||
/// Si no se proporciona un `id`, se genera un identificador único en la forma `<tipo>-<número>`
|
/// Devuelve `false` en caso contrario. Usar cuando solo interesa borrar la entrada.
|
||||||
/// donde `<tipo>` es el nombre corto del tipo en minúsculas (sin espacios) y `<número>` es un
|
///
|
||||||
/// contador interno incremental.
|
/// # Ejemplos
|
||||||
pub fn required_id<T>(&mut self, id: Option<String>) -> String {
|
///
|
||||||
if let Some(id) = id {
|
/// ```rust
|
||||||
id
|
/// use pagetop::prelude::*;
|
||||||
} else {
|
///
|
||||||
let prefix = TypeInfo::ShortName
|
/// let mut cx = Context::new(None).with_param("temp", 1u8);
|
||||||
.of::<T>()
|
/// assert!(cx.remove_param("temp"));
|
||||||
.trim()
|
/// assert!(!cx.remove_param("temp")); // ya no existe
|
||||||
.replace(' ', "_")
|
/// ```
|
||||||
.to_lowercase();
|
pub fn remove_param(&mut self, key: &'static str) -> bool {
|
||||||
let prefix = if prefix.is_empty() {
|
self.params.remove(key).is_some()
|
||||||
"prefix".to_owned()
|
|
||||||
} else {
|
|
||||||
prefix
|
|
||||||
};
|
|
||||||
self.id_counter += 1;
|
|
||||||
join!(prefix, "-", self.id_counter.to_string())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -423,3 +325,173 @@ impl LangId for Context {
|
||||||
self.langid
|
self.langid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Contextual for Context {
|
||||||
|
// Contextual BUILDER **************************************************************************
|
||||||
|
|
||||||
|
#[builder_fn]
|
||||||
|
fn with_request(mut self, request: Option<HttpRequest>) -> Self {
|
||||||
|
self.request = request;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[builder_fn]
|
||||||
|
fn with_langid(mut self, language: &impl LangId) -> Self {
|
||||||
|
self.langid = language.langid();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asigna el tema para renderizar el documento.
|
||||||
|
///
|
||||||
|
/// Localiza el tema por su [`short_name()`](crate::core::AnyInfo::short_name), y si no aplica
|
||||||
|
/// ninguno entonces usará el tema por defecto.
|
||||||
|
#[builder_fn]
|
||||||
|
fn with_theme(mut self, theme_name: &'static str) -> Self {
|
||||||
|
self.theme = theme_by_short_name(theme_name).unwrap_or(*DEFAULT_THEME);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[builder_fn]
|
||||||
|
fn with_layout(mut self, layout_name: &'static str) -> Self {
|
||||||
|
self.layout = layout_name;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Añade o modifica un parámetro dinámico del contexto.
|
||||||
|
///
|
||||||
|
/// El valor se guarda conservando el *nombre del tipo* real para mejorar los mensajes de error
|
||||||
|
/// posteriores.
|
||||||
|
///
|
||||||
|
/// # Ejemplos
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use pagetop::prelude::*;
|
||||||
|
///
|
||||||
|
/// let cx = Context::new(None)
|
||||||
|
/// .with_param("usuario_id", 42_i32)
|
||||||
|
/// .with_param("titulo", String::from("Hola"))
|
||||||
|
/// .with_param("flags", vec!["a", "b"]);
|
||||||
|
/// ```
|
||||||
|
#[builder_fn]
|
||||||
|
fn with_param<T: 'static>(mut self, key: &'static str, value: T) -> Self {
|
||||||
|
let type_name = TypeInfo::FullName.of::<T>();
|
||||||
|
self.params.insert(key, (Box::new(value), type_name));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[builder_fn]
|
||||||
|
fn with_assets(mut self, op: AssetsOp) -> Self {
|
||||||
|
match op {
|
||||||
|
// Favicon.
|
||||||
|
AssetsOp::SetFavicon(favicon) => {
|
||||||
|
self.favicon = favicon;
|
||||||
|
}
|
||||||
|
AssetsOp::SetFaviconIfNone(icon) => {
|
||||||
|
if self.favicon.is_none() {
|
||||||
|
self.favicon = Some(icon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Stylesheets.
|
||||||
|
AssetsOp::AddStyleSheet(css) => {
|
||||||
|
self.stylesheets.add(css);
|
||||||
|
}
|
||||||
|
AssetsOp::RemoveStyleSheet(path) => {
|
||||||
|
self.stylesheets.remove(path);
|
||||||
|
}
|
||||||
|
// JavaScripts.
|
||||||
|
AssetsOp::AddJavaScript(js) => {
|
||||||
|
self.javascripts.add(js);
|
||||||
|
}
|
||||||
|
AssetsOp::RemoveJavaScript(path) => {
|
||||||
|
self.javascripts.remove(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contextual GETTERS **************************************************************************
|
||||||
|
|
||||||
|
fn request(&self) -> Option<&HttpRequest> {
|
||||||
|
self.request.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn theme(&self) -> ThemeRef {
|
||||||
|
self.theme
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layout(&self) -> &str {
|
||||||
|
self.layout
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Recupera un parámetro como [`Option`], simplificando el acceso.
|
||||||
|
///
|
||||||
|
/// A diferencia de [`get_param`](Self::get_param), que devuelve un [`Result`] con información
|
||||||
|
/// detallada de error, este método devuelve `None` tanto si la clave no existe como si el valor
|
||||||
|
/// guardado no coincide con el tipo solicitado.
|
||||||
|
///
|
||||||
|
/// Resulta útil en escenarios donde sólo interesa saber si el valor existe y es del tipo
|
||||||
|
/// correcto, sin necesidad de diferenciar entre error de ausencia o de tipo.
|
||||||
|
///
|
||||||
|
/// # Ejemplo
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use pagetop::prelude::*;
|
||||||
|
///
|
||||||
|
/// let cx = Context::new(None).with_param("username", String::from("Alice"));
|
||||||
|
///
|
||||||
|
/// // Devuelve Some(&String) si existe y coincide el tipo.
|
||||||
|
/// assert_eq!(cx.param::<String>("username").map(|s| s.as_str()), Some("Alice"));
|
||||||
|
///
|
||||||
|
/// // Devuelve None si no existe o si el tipo no coincide.
|
||||||
|
/// assert!(cx.param::<i32>("username").is_none());
|
||||||
|
/// assert!(cx.param::<String>("missing").is_none());
|
||||||
|
///
|
||||||
|
/// // Acceso con valor por defecto.
|
||||||
|
/// let user = cx.param::<String>("missing")
|
||||||
|
/// .cloned()
|
||||||
|
/// .unwrap_or_else(|| "visitor".to_string());
|
||||||
|
/// assert_eq!(user, "visitor");
|
||||||
|
/// ```
|
||||||
|
fn param<T: 'static>(&self, key: &'static str) -> Option<&T> {
|
||||||
|
self.get_param::<T>(key).ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn favicon(&self) -> Option<&Favicon> {
|
||||||
|
self.favicon.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stylesheets(&self) -> &Assets<StyleSheet> {
|
||||||
|
&self.stylesheets
|
||||||
|
}
|
||||||
|
|
||||||
|
fn javascripts(&self) -> &Assets<JavaScript> {
|
||||||
|
&self.javascripts
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contextual HELPERS **************************************************************************
|
||||||
|
|
||||||
|
/// Devuelve un identificador único dentro del contexto para el tipo `T`, si no se proporciona
|
||||||
|
/// un `id` explícito.
|
||||||
|
///
|
||||||
|
/// Si no se proporciona un `id`, se genera un identificador único en la forma `<tipo>-<número>`
|
||||||
|
/// donde `<tipo>` es el nombre corto del tipo en minúsculas (sin espacios) y `<número>` es un
|
||||||
|
/// contador interno incremental.
|
||||||
|
fn required_id<T>(&mut self, id: Option<String>) -> String {
|
||||||
|
if let Some(id) = id {
|
||||||
|
id
|
||||||
|
} else {
|
||||||
|
let prefix = TypeInfo::ShortName
|
||||||
|
.of::<T>()
|
||||||
|
.trim()
|
||||||
|
.replace(' ', "_")
|
||||||
|
.to_lowercase();
|
||||||
|
let prefix = if prefix.is_empty() {
|
||||||
|
"prefix".to_owned()
|
||||||
|
} else {
|
||||||
|
prefix
|
||||||
|
};
|
||||||
|
self.id_counter += 1;
|
||||||
|
join!(prefix, "-", self.id_counter.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -8,7 +8,8 @@ use crate::builder_fn;
|
||||||
use crate::core::component::{Child, ChildOp, Component};
|
use crate::core::component::{Child, ChildOp, Component};
|
||||||
use crate::core::theme::{ChildrenInRegions, ThemeRef, REGION_CONTENT};
|
use crate::core::theme::{ChildrenInRegions, ThemeRef, REGION_CONTENT};
|
||||||
use crate::html::{html, Markup, DOCTYPE};
|
use crate::html::{html, Markup, DOCTYPE};
|
||||||
use crate::html::{AssetsOp, Context};
|
use crate::html::{Assets, Favicon, JavaScript, StyleSheet};
|
||||||
|
use crate::html::{AssetsOp, Context, Contextual};
|
||||||
use crate::html::{AttrClasses, ClassesOp};
|
use crate::html::{AttrClasses, ClassesOp};
|
||||||
use crate::html::{AttrId, AttrL10n};
|
use crate::html::{AttrId, AttrL10n};
|
||||||
use crate::locale::{CharacterDirection, L10n, LangId, LanguageIdentifier};
|
use crate::locale::{CharacterDirection, L10n, LangId, LanguageIdentifier};
|
||||||
|
@ -25,9 +26,9 @@ pub struct Page {
|
||||||
description : AttrL10n,
|
description : AttrL10n,
|
||||||
metadata : Vec<(&'static str, &'static str)>,
|
metadata : Vec<(&'static str, &'static str)>,
|
||||||
properties : Vec<(&'static str, &'static str)>,
|
properties : Vec<(&'static str, &'static str)>,
|
||||||
context : Context,
|
|
||||||
body_id : AttrId,
|
body_id : AttrId,
|
||||||
body_classes: AttrClasses,
|
body_classes: AttrClasses,
|
||||||
|
context : Context,
|
||||||
regions : ChildrenInRegions,
|
regions : ChildrenInRegions,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,9 +44,9 @@ impl Page {
|
||||||
description : AttrL10n::default(),
|
description : AttrL10n::default(),
|
||||||
metadata : Vec::default(),
|
metadata : Vec::default(),
|
||||||
properties : Vec::default(),
|
properties : Vec::default(),
|
||||||
context : Context::new(request),
|
|
||||||
body_id : AttrId::default(),
|
body_id : AttrId::default(),
|
||||||
body_classes: AttrClasses::default(),
|
body_classes: AttrClasses::default(),
|
||||||
|
context : Context::new(request),
|
||||||
regions : ChildrenInRegions::default(),
|
regions : ChildrenInRegions::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,40 +81,6 @@ impl Page {
|
||||||
self
|
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
|
|
||||||
}
|
|
||||||
|
|
||||||
#[builder_fn]
|
|
||||||
pub fn with_param<T: 'static>(mut self, key: &'static str, value: T) -> Self {
|
|
||||||
self.context.alter_param(key, value);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Establece el atributo `id` del elemento `<body>`.
|
/// Establece el atributo `id` del elemento `<body>`.
|
||||||
#[builder_fn]
|
#[builder_fn]
|
||||||
pub fn with_body_id(mut self, id: impl AsRef<str>) -> Self {
|
pub fn with_body_id(mut self, id: impl AsRef<str>) -> Self {
|
||||||
|
@ -205,25 +172,6 @@ impl Page {
|
||||||
&self.properties
|
&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()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn param<T: 'static>(&self, key: &'static str) -> Option<&T> {
|
|
||||||
self.context.param(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Devuelve el identificador del elemento `<body>`.
|
/// Devuelve el identificador del elemento `<body>`.
|
||||||
pub fn body_id(&self) -> &AttrId {
|
pub fn body_id(&self) -> &AttrId {
|
||||||
&self.body_id
|
&self.body_id
|
||||||
|
@ -233,7 +181,7 @@ impl Page {
|
||||||
pub fn body_classes(&self) -> &AttrClasses {
|
pub fn body_classes(&self) -> &AttrClasses {
|
||||||
&self.body_classes
|
&self.body_classes
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
/// Devuelve una referencia mutable al [`Context`] de la página.
|
/// Devuelve una referencia mutable al [`Context`] de la página.
|
||||||
///
|
///
|
||||||
/// El [`Context`] actúa como intermediario para muchos métodos de `Page` (idioma, tema,
|
/// El [`Context`] actúa como intermediario para muchos métodos de `Page` (idioma, tema,
|
||||||
|
@ -242,10 +190,10 @@ impl Page {
|
||||||
pub fn context(&mut self) -> &mut Context {
|
pub fn context(&mut self) -> &mut Context {
|
||||||
&mut self.context
|
&mut self.context
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
// Page RENDER *********************************************************************************
|
// Page RENDER *********************************************************************************
|
||||||
|
|
||||||
/// Renderiza los componentes de una región (`regiona_name`) de la página.
|
/// Renderiza los componentes de una región (`region_name`) de la página.
|
||||||
pub fn render_region(&mut self, region_name: &'static str) -> Markup {
|
pub fn render_region(&mut self, region_name: &'static str) -> Markup {
|
||||||
self.regions
|
self.regions
|
||||||
.merge_all_components(self.context.theme(), region_name)
|
.merge_all_components(self.context.theme(), region_name)
|
||||||
|
@ -302,3 +250,79 @@ impl LangId for Page {
|
||||||
self.context.langid()
|
self.context.langid()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Contextual for Page {
|
||||||
|
// Contextual BUILDER **************************************************************************
|
||||||
|
|
||||||
|
#[builder_fn]
|
||||||
|
fn with_request(mut self, request: Option<HttpRequest>) -> Self {
|
||||||
|
self.context.alter_request(request);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[builder_fn]
|
||||||
|
fn with_langid(mut self, language: &impl LangId) -> Self {
|
||||||
|
self.context.alter_langid(language);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[builder_fn]
|
||||||
|
fn with_theme(mut self, theme_name: &'static str) -> Self {
|
||||||
|
self.context.alter_theme(theme_name);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[builder_fn]
|
||||||
|
fn with_layout(mut self, layout_name: &'static str) -> Self {
|
||||||
|
self.context.alter_layout(layout_name);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[builder_fn]
|
||||||
|
fn with_param<T: 'static>(mut self, key: &'static str, value: T) -> Self {
|
||||||
|
self.context.alter_param(key, value);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[builder_fn]
|
||||||
|
fn with_assets(mut self, op: AssetsOp) -> Self {
|
||||||
|
self.context.alter_assets(op);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contextual GETTERS **************************************************************************
|
||||||
|
|
||||||
|
fn request(&self) -> Option<&HttpRequest> {
|
||||||
|
self.context.request()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn theme(&self) -> ThemeRef {
|
||||||
|
self.context.theme()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layout(&self) -> &str {
|
||||||
|
self.context.layout()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn param<T: 'static>(&self, key: &'static str) -> Option<&T> {
|
||||||
|
self.context.param(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn favicon(&self) -> Option<&Favicon> {
|
||||||
|
self.context.favicon()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stylesheets(&self) -> &Assets<StyleSheet> {
|
||||||
|
self.context.stylesheets()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn javascripts(&self) -> &Assets<JavaScript> {
|
||||||
|
self.context.javascripts()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contextual HELPERS **************************************************************************
|
||||||
|
|
||||||
|
fn required_id<T>(&mut self, id: Option<String>) -> String {
|
||||||
|
self.context.required_id::<T>(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::base::component::Html;
|
use crate::base::component::Html;
|
||||||
|
use crate::html::Contextual;
|
||||||
use crate::locale::L10n;
|
use crate::locale::L10n;
|
||||||
use crate::response::ResponseError;
|
use crate::response::ResponseError;
|
||||||
use crate::service::http::{header::ContentType, StatusCode};
|
use crate::service::http::{header::ContentType, StatusCode};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue