diff --git a/Cargo.lock b/Cargo.lock index ab7b20a..b03e980 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1413,7 +1413,7 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "pagetop" -version = "0.0.11" +version = "0.0.12" dependencies = [ "actix-files", "actix-web", @@ -1426,6 +1426,7 @@ dependencies = [ "fluent-templates", "itoa", "pagetop-macros", + "parking_lot", "pastey", "serde", "static-files", @@ -2212,9 +2213,9 @@ dependencies = [ [[package]] name = "tracing-actix-web" -version = "0.7.18" +version = "0.7.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2340b7722695166c7fc9b3e3cd1166e7c74fedb9075b8f0c74d3822d2e41caf5" +checksum = "5360edd490ec8dee9fedfc6a9fd83ac2f01b3e1996e3261b9ad18a61971fe064" dependencies = [ "actix-web", "mutually_exclusive_features", diff --git a/Cargo.toml b/Cargo.toml index b6f844f..049c258 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop" -version = "0.0.11" +version = "0.0.12" edition = "2021" description = """\ @@ -21,6 +21,7 @@ concat-string = "1.0.1" config = { version = "0.15.13", default-features = false, features = ["toml"] } figlet-rs = "0.1.5" itoa = "1.0.15" +parking_lot = "0.12.4" paste = { package = "pastey", version = "0.1.0" } substring = "1.4.5" terminal_size = "0.4.2" @@ -28,7 +29,7 @@ terminal_size = "0.4.2" tracing = "0.1.41" tracing-appender = "0.2.3" tracing-subscriber = { version = "0.3.19", features = ["json", "env-filter"] } -tracing-actix-web = "0.7.18" +tracing-actix-web = "0.7.19" fluent-templates = "0.13.0" unic-langid = { version = "0.9.6", features = ["macros"] } diff --git a/src/base.rs b/src/base.rs index c50cc9e..491223e 100644 --- a/src/base.rs +++ b/src/base.rs @@ -1,3 +1,5 @@ -//! Reúne temas listos para usar. +//! Reúne acciones y temas listos para usar. + +pub mod action; pub mod theme; diff --git a/src/base/action.rs b/src/base/action.rs new file mode 100644 index 0000000..119a7ea --- /dev/null +++ b/src/base/action.rs @@ -0,0 +1,15 @@ +//! Acciones predefinidas para alterar el funcionamiento interno de `PageTop`. + +use crate::prelude::*; + +/// Tipo de función para manipular componentes y su contexto de renderizado. +/// +/// Se usa en acciones definidas en [`component`] y [`theme`] para alterar el comportamiento de los +/// componentes. +/// +/// Recibe referencias mutables (`&mut`) del componente `component` y del contexto `cx`. +pub type FnActionWithComponent = fn(component: &mut C, cx: &mut Context); + +pub mod component; + +pub mod theme; diff --git a/src/base/action/component.rs b/src/base/action/component.rs new file mode 100644 index 0000000..aaef1ce --- /dev/null +++ b/src/base/action/component.rs @@ -0,0 +1,10 @@ +//! Acciones que operan sobre componentes. + +mod is_renderable; +pub use is_renderable::*; + +mod before_render_component; +pub use before_render_component::*; + +mod after_render_component; +pub use after_render_component::*; diff --git a/src/base/action/component/after_render_component.rs b/src/base/action/component/after_render_component.rs new file mode 100644 index 0000000..3c8ead9 --- /dev/null +++ b/src/base/action/component/after_render_component.rs @@ -0,0 +1,81 @@ +use crate::prelude::*; + +use crate::base::action::FnActionWithComponent; + +/// Ejecuta [`FnActionWithComponent`] después de renderizar un componente. +pub struct AfterRender { + f: FnActionWithComponent, + referer_type_id: Option, + referer_id: OptionId, + weight: Weight, +} + +/// Filtro para despachar [`FnActionWithComponent`] después de renderizar un componente `C`. +impl ActionDispatcher for AfterRender { + /// Devuelve el identificador de tipo ([`UniqueId`]) del componente `C`. + fn referer_type_id(&self) -> Option { + self.referer_type_id + } + + /// Devuelve el identificador del componente. + fn referer_id(&self) -> Option { + self.referer_id.get() + } + + /// Devuelve el peso para definir el orden de aplicación. + fn weight(&self) -> Weight { + self.weight + } +} + +impl AfterRender { + /// Permite [registrar](ExtensionTrait::actions) una nueva acción [`FnActionWithComponent`]. + pub fn new(f: FnActionWithComponent) -> Self { + AfterRender { + f, + referer_type_id: Some(UniqueId::of::()), + referer_id: OptionId::default(), + weight: 0, + } + } + + /// Afina el registro para ejecutar la acción [`FnActionWithComponent`] sólo para el componente + /// `C` con identificador `id`. + pub fn filter_by_referer_id(mut self, id: impl AsRef) -> Self { + self.referer_id.alter_value(id); + self + } + + /// Opcional. Acciones con pesos más bajos se aplican antes. Se pueden usar valores negativos. + pub fn with_weight(mut self, value: Weight) -> Self { + self.weight = value; + self + } + + // Despacha las acciones. + #[inline] + pub(crate) fn dispatch(component: &mut C, cx: &mut Context) { + // Primero despacha las acciones para el tipo de componente. + dispatch_actions( + &ActionKey::new( + UniqueId::of::(), + None, + Some(UniqueId::of::()), + None, + ), + |action: &Self| (action.f)(component, cx), + ); + // Y luego despacha las acciones para el tipo de componente con un identificador dado. + if let Some(id) = component.id() { + dispatch_actions( + &ActionKey::new( + UniqueId::of::(), + None, + Some(UniqueId::of::()), + Some(id), + ), + |action: &Self| (action.f)(component, cx), + ); + } + } +} diff --git a/src/base/action/component/before_render_component.rs b/src/base/action/component/before_render_component.rs new file mode 100644 index 0000000..0ebe409 --- /dev/null +++ b/src/base/action/component/before_render_component.rs @@ -0,0 +1,81 @@ +use crate::prelude::*; + +use crate::base::action::FnActionWithComponent; + +/// Ejecuta [`FnActionWithComponent`] antes de renderizar el componente. +pub struct BeforeRender { + f: FnActionWithComponent, + referer_type_id: Option, + referer_id: OptionId, + weight: Weight, +} + +/// Filtro para despachar [`FnActionWithComponent`] antes de renderizar un componente `C`. +impl ActionDispatcher for BeforeRender { + /// Devuelve el identificador de tipo ([`UniqueId`]) del componente `C`. + fn referer_type_id(&self) -> Option { + self.referer_type_id + } + + /// Devuelve el identificador del componente. + fn referer_id(&self) -> Option { + self.referer_id.get() + } + + /// Devuelve el peso para definir el orden de aplicación. + fn weight(&self) -> Weight { + self.weight + } +} + +impl BeforeRender { + /// Permite [registrar](ExtensionTrait::actions) una nueva acción [`FnActionWithComponent`]. + pub fn new(f: FnActionWithComponent) -> Self { + BeforeRender { + f, + referer_type_id: Some(UniqueId::of::()), + referer_id: OptionId::default(), + weight: 0, + } + } + + /// Afina el registro para ejecutar la acción [`FnActionWithComponent`] sólo para el componente + /// `C` con identificador `id`. + pub fn filter_by_referer_id(mut self, id: impl AsRef) -> Self { + self.referer_id.alter_value(id); + self + } + + /// Opcional. Acciones con pesos más bajos se aplican antes. Se pueden usar valores negativos. + pub fn with_weight(mut self, value: Weight) -> Self { + self.weight = value; + self + } + + // Despacha las acciones. + #[inline] + pub(crate) fn dispatch(component: &mut C, cx: &mut Context) { + // Primero despacha las acciones para el tipo de componente. + dispatch_actions( + &ActionKey::new( + UniqueId::of::(), + None, + Some(UniqueId::of::()), + None, + ), + |action: &Self| (action.f)(component, cx), + ); + // Y luego despacha las aciones para el tipo de componente con un identificador dado. + if let Some(id) = component.id() { + dispatch_actions( + &ActionKey::new( + UniqueId::of::(), + None, + Some(UniqueId::of::()), + Some(id), + ), + |action: &Self| (action.f)(component, cx), + ); + } + } +} diff --git a/src/base/action/component/is_renderable.rs b/src/base/action/component/is_renderable.rs new file mode 100644 index 0000000..7ba7d53 --- /dev/null +++ b/src/base/action/component/is_renderable.rs @@ -0,0 +1,96 @@ +use crate::prelude::*; + +/// Tipo de función para determinar si un componente se renderiza o no. +/// +/// Se usa en la acción [`IsRenderable`] para controlar dinámicamente la visibilidad del componente +/// `component` según el contexto `cx`. El componente **no se renderiza** en cuanto una de las +/// funciones devuelva `false`. +pub type FnIsRenderable = fn(component: &C, cx: &Context) -> bool; + +/// Con la función [`FnIsRenderable`] se puede decidir si se renderiza o no un componente. +pub struct IsRenderable { + f: FnIsRenderable, + referer_type_id: Option, + referer_id: OptionId, + weight: Weight, +} + +/// Filtro para despachar [`FnIsRenderable`] para decidir si se renderiza o no un componente `C`. +impl ActionDispatcher for IsRenderable { + /// Devuelve el identificador de tipo ([`UniqueId`]) del componente `C`. + fn referer_type_id(&self) -> Option { + self.referer_type_id + } + + /// Devuelve el identificador del componente. + fn referer_id(&self) -> Option { + self.referer_id.get() + } + + /// Devuelve el peso para definir el orden de aplicación. + fn weight(&self) -> Weight { + self.weight + } +} + +impl IsRenderable { + /// Permite [registrar](ExtensionTrait::actions) una nueva acción [`FnIsRenderable`]. + pub fn new(f: FnIsRenderable) -> Self { + IsRenderable { + f, + referer_type_id: Some(UniqueId::of::()), + referer_id: OptionId::default(), + weight: 0, + } + } + + /// Afina el registro para ejecutar la acción [`FnIsRenderable`] sólo para el componente `C` + /// con identificador `id`. + pub fn filter_by_referer_id(mut self, id: impl AsRef) -> Self { + self.referer_id.alter_value(id); + self + } + + /// Opcional. Acciones con pesos más bajos se aplican antes. Se pueden usar valores negativos. + pub fn with_weight(mut self, value: Weight) -> Self { + self.weight = value; + self + } + + // Despacha las acciones. Se detiene en cuanto una [`FnIsRenderable`] devuelve `false`. + #[inline] + pub(crate) fn dispatch(component: &C, cx: &mut Context) -> bool { + let mut renderable = true; + dispatch_actions( + &ActionKey::new( + UniqueId::of::(), + None, + Some(UniqueId::of::()), + None, + ), + |action: &Self| { + if renderable && !(action.f)(component, cx) { + renderable = false; + } + }, + ); + if renderable { + if let Some(id) = component.id() { + dispatch_actions( + &ActionKey::new( + UniqueId::of::(), + None, + Some(UniqueId::of::()), + Some(id), + ), + |action: &Self| { + if renderable && !(action.f)(component, cx) { + renderable = false; + } + }, + ); + } + } + renderable + } +} diff --git a/src/base/action/theme.rs b/src/base/action/theme.rs new file mode 100644 index 0000000..4098857 --- /dev/null +++ b/src/base/action/theme.rs @@ -0,0 +1,10 @@ +//! Acciones lanzadas desde los temas. + +mod before_render_component; +pub use before_render_component::*; + +mod after_render_component; +pub use after_render_component::*; + +mod prepare_render; +pub use prepare_render::*; diff --git a/src/base/action/theme/after_render_component.rs b/src/base/action/theme/after_render_component.rs new file mode 100644 index 0000000..d33e0d2 --- /dev/null +++ b/src/base/action/theme/after_render_component.rs @@ -0,0 +1,50 @@ +use crate::prelude::*; + +use crate::base::action::FnActionWithComponent; + +/// Ejecuta [`FnActionWithComponent`] después de que un tema renderice el componente. +pub struct AfterRender { + f: FnActionWithComponent, + theme_type_id: Option, + referer_type_id: Option, +} + +/// Filtro para despachar [`FnActionWithComponent`] después de que un tema renderice el componente +/// `C`. +impl ActionDispatcher for AfterRender { + /// Devuelve el identificador de tipo ([`UniqueId`]) del tema. + fn theme_type_id(&self) -> Option { + self.theme_type_id + } + + /// Devuelve el identificador de tipo ([`UniqueId`]) del componente `C`. + fn referer_type_id(&self) -> Option { + self.referer_type_id + } +} + +impl AfterRender { + /// Permite [registrar](ExtensionTrait::actions) una nueva acción [`FnActionWithComponent`] para + /// un tema dado. + pub fn new(theme: ThemeRef, f: FnActionWithComponent) -> Self { + AfterRender { + f, + theme_type_id: Some(theme.type_id()), + referer_type_id: Some(UniqueId::of::()), + } + } + + // Despacha las acciones. + #[inline] + pub(crate) fn dispatch(component: &mut C, cx: &mut Context) { + dispatch_actions( + &ActionKey::new( + UniqueId::of::(), + Some(cx.theme().type_id()), + Some(UniqueId::of::()), + None, + ), + |action: &Self| (action.f)(component, cx), + ); + } +} diff --git a/src/base/action/theme/before_render_component.rs b/src/base/action/theme/before_render_component.rs new file mode 100644 index 0000000..76f6cd2 --- /dev/null +++ b/src/base/action/theme/before_render_component.rs @@ -0,0 +1,50 @@ +use crate::prelude::*; + +use crate::base::action::FnActionWithComponent; + +/// Ejecuta [`FnActionWithComponent`] antes de que un tema renderice el componente. +pub struct BeforeRender { + f: FnActionWithComponent, + theme_type_id: Option, + referer_type_id: Option, +} + +/// Filtro para despachar [`FnActionWithComponent`] antes de que un tema renderice el componente +/// `C`. +impl ActionDispatcher for BeforeRender { + /// Devuelve el identificador de tipo ([`UniqueId`]) del tema. + fn theme_type_id(&self) -> Option { + self.theme_type_id + } + + /// Devuelve el identificador de tipo ([`UniqueId`]) del componente `C`. + fn referer_type_id(&self) -> Option { + self.referer_type_id + } +} + +impl BeforeRender { + /// Permite [registrar](ExtensionTrait::actions) una nueva acción [`FnActionWithComponent`] para + /// un tema dado. + pub fn new(theme: ThemeRef, f: FnActionWithComponent) -> Self { + BeforeRender { + f, + theme_type_id: Some(theme.type_id()), + referer_type_id: Some(UniqueId::of::()), + } + } + + // Despacha las acciones. + #[inline] + pub(crate) fn dispatch(component: &mut C, cx: &mut Context) { + dispatch_actions( + &ActionKey::new( + UniqueId::of::(), + Some(cx.theme().type_id()), + Some(UniqueId::of::()), + None, + ), + |action: &Self| (action.f)(component, cx), + ); + } +} diff --git a/src/base/action/theme/prepare_render.rs b/src/base/action/theme/prepare_render.rs new file mode 100644 index 0000000..9d3e261 --- /dev/null +++ b/src/base/action/theme/prepare_render.rs @@ -0,0 +1,63 @@ +use crate::prelude::*; + +/// Tipo de función para alterar el renderizado de un componente. +/// +/// Permite a un [tema](crate::base::action::theme) sobreescribir el renderizado predeterminado de +/// los componentes. +/// +/// Recibe una referencia al componente `component` y una referencia mutable al contexto `cx`. +pub type FnPrepareRender = fn(component: &C, cx: &mut Context) -> PrepareMarkup; + +/// Ejecuta [`FnPrepareRender`] para preparar el renderizado de un componente. +/// +/// Permite a un tema hacer una implementación nueva del renderizado de un componente. +pub struct PrepareRender { + f: FnPrepareRender, + theme_type_id: Option, + referer_type_id: Option, +} + +/// Filtro para despachar [`FnPrepareRender`] que modifica el renderizado de un componente `C`. +impl ActionDispatcher for PrepareRender { + /// Devuelve el identificador de tipo ([`UniqueId`]) del tema. + fn theme_type_id(&self) -> Option { + self.theme_type_id + } + + /// Devuelve el identificador de tipo ([`UniqueId`]) del componente `C`. + fn referer_type_id(&self) -> Option { + self.referer_type_id + } +} + +impl PrepareRender { + /// Permite [registrar](ExtensionTrait::actions) una nueva acción [`FnPrepareRender`] para un + /// tema dado. + pub fn new(theme: ThemeRef, f: FnPrepareRender) -> Self { + PrepareRender { + f, + theme_type_id: Some(theme.type_id()), + referer_type_id: Some(UniqueId::of::()), + } + } + + // Despacha las acciones. Se detiene en cuanto una renderiza. + #[inline] + pub(crate) fn dispatch(component: &C, cx: &mut Context) -> PrepareMarkup { + let mut render_component = PrepareMarkup::None; + dispatch_actions( + &ActionKey::new( + UniqueId::of::(), + Some(cx.theme().type_id()), + Some(UniqueId::of::()), + None, + ), + |action: &Self| { + if render_component.is_empty() { + render_component = (action.f)(component, cx); + } + }, + ); + render_component + } +} diff --git a/src/core.rs b/src/core.rs index 4b857fc..2168c93 100644 --- a/src/core.rs +++ b/src/core.rs @@ -1,4 +1,4 @@ -//! Tipos y funciones esenciales para crear acciones, extensiones y temas. +//! Tipos y funciones esenciales para crear acciones, componentes, extensiones y temas. use std::any::Any; @@ -204,6 +204,9 @@ impl AnyCast for T {} // API para definir acciones que alteran el comportamiento predeterminado del código. pub mod action; +// API para construir nuevos componentes. +pub mod component; + // API para añadir nuevas funcionalidades usando extensiones. pub mod extension; diff --git a/src/core/action.rs b/src/core/action.rs index 8503ae9..f5dc11c 100644 --- a/src/core/action.rs +++ b/src/core/action.rs @@ -1,11 +1,11 @@ //! API para definir acciones que inyectan código en el flujo de la aplicación. //! -//! Permite crear acciones en las librerías para que otros *crates* puedan inyectar código usando -//! funciones *ad hoc* que modifican el comportamiento predefinido en puntos concretos del flujo de -//! ejecución de la aplicación. +//! Permite crear acciones para que otros *crates* puedan inyectar código usando funciones *ad hoc* +//! que modifican el comportamiento predefinido en puntos concretos del flujo de ejecución de la +//! aplicación. mod definition; -pub use definition::{ActionBase, ActionBox, ActionKey, ActionTrait}; +pub use definition::{ActionBox, ActionDispatcher, ActionKey}; mod list; use list::ActionsList; @@ -14,49 +14,33 @@ mod all; pub(crate) use all::add_action; pub use all::dispatch_actions; -/// Crea una lista de acciones para facilitar la implementación del método -/// [`actions`](crate::core::extension::ExtensionTrait#method.actions). +/// Facilita la implementación del método +/// [`actions()`](crate::core::extension::ExtensionTrait::actions). /// -/// Esta macro crea vectores de [`ActionBox`], el tipo dinámico que encapsula cualquier acción que -/// implemente [`ActionTrait`]. Evita escribir repetidamente `Box::new(...)` para cada acción -/// inyectada, manteniendo el código más limpio. +/// Evita escribir repetidamente `Box::new(...)` para cada acción de la lista, manteniendo el código +/// más limpio. /// -/// # Ejemplos -/// -/// Puede llamarse sin argumentos para crear un vector vacío: +/// # Ejemplo /// /// ```rust,ignore -/// let my_actions = inject_actions![]; -/// ``` +/// use pagetop::prelude::*; /// -/// O con una lista de acciones concretas: -/// -/// ```rust,ignore -/// let my_actions = inject_actions![ -/// MyFirstAction::new(), -/// MySecondAction::new().with_weight(10), -/// ]; -/// ``` -/// -/// Internamente, expande a un `vec![Box::new(...), ...]`. -/// -/// # Ejemplo típico en una extensión -/// -/// ```rust,ignore -/// impl ExtensionTrait for MyExtension { +/// impl ExtensionTrait for MyTheme { /// fn actions(&self) -> Vec { -/// inject_actions![ -/// CustomizeLoginAction::new(), -/// ModifyHeaderAction::new().with_weight(-5), +/// actions_boxed![ +/// action::theme::BeforeRender::