Compare commits

..

No commits in common. "ed25a17e80279f72160d91db3166c258c95a39fe" and "f76a208520945e5ff7b68ce97805e5ea27434804" have entirely different histories.

30 changed files with 147 additions and 1135 deletions

7
Cargo.lock generated
View file

@ -1413,7 +1413,7 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "pagetop"
version = "0.0.12"
version = "0.0.11"
dependencies = [
"actix-files",
"actix-web",
@ -1426,7 +1426,6 @@ dependencies = [
"fluent-templates",
"itoa",
"pagetop-macros",
"parking_lot",
"pastey",
"serde",
"static-files",
@ -2213,9 +2212,9 @@ dependencies = [
[[package]]
name = "tracing-actix-web"
version = "0.7.19"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5360edd490ec8dee9fedfc6a9fd83ac2f01b3e1996e3261b9ad18a61971fe064"
checksum = "2340b7722695166c7fc9b3e3cd1166e7c74fedb9075b8f0c74d3822d2e41caf5"
dependencies = [
"actix-web",
"mutually_exclusive_features",

View file

@ -1,6 +1,6 @@
[package]
name = "pagetop"
version = "0.0.12"
version = "0.0.11"
edition = "2021"
description = """\
@ -21,7 +21,6 @@ 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"
@ -29,7 +28,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.19"
tracing-actix-web = "0.7.18"
fluent-templates = "0.13.0"
unic-langid = { version = "0.9.6", features = ["macros"] }

View file

@ -1,7 +1,3 @@
//! Reúne acciones, componentes y temas listos para usar.
pub mod action;
pub mod component;
//! Reúne temas listos para usar.
pub mod theme;

View file

@ -1,15 +0,0 @@
//! 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<C> = fn(component: &mut C, cx: &mut Context);
pub mod component;
pub mod theme;

View file

@ -1,10 +0,0 @@
//! 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::*;

View file

@ -1,81 +0,0 @@
use crate::prelude::*;
use crate::base::action::FnActionWithComponent;
/// Ejecuta [`FnActionWithComponent`] después de renderizar un componente.
pub struct AfterRender<C: ComponentTrait> {
f: FnActionWithComponent<C>,
referer_type_id: Option<UniqueId>,
referer_id: OptionId,
weight: Weight,
}
/// Filtro para despachar [`FnActionWithComponent`] después de renderizar un componente `C`.
impl<C: ComponentTrait> ActionDispatcher for AfterRender<C> {
/// Devuelve el identificador de tipo ([`UniqueId`]) del componente `C`.
fn referer_type_id(&self) -> Option<UniqueId> {
self.referer_type_id
}
/// Devuelve el identificador del componente.
fn referer_id(&self) -> Option<String> {
self.referer_id.get()
}
/// Devuelve el peso para definir el orden de aplicación.
fn weight(&self) -> Weight {
self.weight
}
}
impl<C: ComponentTrait> AfterRender<C> {
/// Permite [registrar](ExtensionTrait::actions) una nueva acción [`FnActionWithComponent`].
pub fn new(f: FnActionWithComponent<C>) -> Self {
AfterRender {
f,
referer_type_id: Some(UniqueId::of::<C>()),
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<str>) -> 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::<Self>(),
None,
Some(UniqueId::of::<C>()),
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::<Self>(),
None,
Some(UniqueId::of::<C>()),
Some(id),
),
|action: &Self| (action.f)(component, cx),
);
}
}
}

View file

@ -1,81 +0,0 @@
use crate::prelude::*;
use crate::base::action::FnActionWithComponent;
/// Ejecuta [`FnActionWithComponent`] antes de renderizar el componente.
pub struct BeforeRender<C: ComponentTrait> {
f: FnActionWithComponent<C>,
referer_type_id: Option<UniqueId>,
referer_id: OptionId,
weight: Weight,
}
/// Filtro para despachar [`FnActionWithComponent`] antes de renderizar un componente `C`.
impl<C: ComponentTrait> ActionDispatcher for BeforeRender<C> {
/// Devuelve el identificador de tipo ([`UniqueId`]) del componente `C`.
fn referer_type_id(&self) -> Option<UniqueId> {
self.referer_type_id
}
/// Devuelve el identificador del componente.
fn referer_id(&self) -> Option<String> {
self.referer_id.get()
}
/// Devuelve el peso para definir el orden de aplicación.
fn weight(&self) -> Weight {
self.weight
}
}
impl<C: ComponentTrait> BeforeRender<C> {
/// Permite [registrar](ExtensionTrait::actions) una nueva acción [`FnActionWithComponent`].
pub fn new(f: FnActionWithComponent<C>) -> Self {
BeforeRender {
f,
referer_type_id: Some(UniqueId::of::<C>()),
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<str>) -> 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::<Self>(),
None,
Some(UniqueId::of::<C>()),
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::<Self>(),
None,
Some(UniqueId::of::<C>()),
Some(id),
),
|action: &Self| (action.f)(component, cx),
);
}
}
}

View file

@ -1,96 +0,0 @@
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<C> = fn(component: &C, cx: &Context) -> bool;
/// Con la función [`FnIsRenderable`] se puede decidir si se renderiza o no un componente.
pub struct IsRenderable<C: ComponentTrait> {
f: FnIsRenderable<C>,
referer_type_id: Option<UniqueId>,
referer_id: OptionId,
weight: Weight,
}
/// Filtro para despachar [`FnIsRenderable`] para decidir si se renderiza o no un componente `C`.
impl<C: ComponentTrait> ActionDispatcher for IsRenderable<C> {
/// Devuelve el identificador de tipo ([`UniqueId`]) del componente `C`.
fn referer_type_id(&self) -> Option<UniqueId> {
self.referer_type_id
}
/// Devuelve el identificador del componente.
fn referer_id(&self) -> Option<String> {
self.referer_id.get()
}
/// Devuelve el peso para definir el orden de aplicación.
fn weight(&self) -> Weight {
self.weight
}
}
impl<C: ComponentTrait> IsRenderable<C> {
/// Permite [registrar](ExtensionTrait::actions) una nueva acción [`FnIsRenderable`].
pub fn new(f: FnIsRenderable<C>) -> Self {
IsRenderable {
f,
referer_type_id: Some(UniqueId::of::<C>()),
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<str>) -> 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::<Self>(),
None,
Some(UniqueId::of::<C>()),
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::<Self>(),
None,
Some(UniqueId::of::<C>()),
Some(id),
),
|action: &Self| {
if renderable && !(action.f)(component, cx) {
renderable = false;
}
},
);
}
}
renderable
}
}

View file

@ -1,10 +0,0 @@
//! 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::*;

View file

@ -1,50 +0,0 @@
use crate::prelude::*;
use crate::base::action::FnActionWithComponent;
/// Ejecuta [`FnActionWithComponent`] después de que un tema renderice el componente.
pub struct AfterRender<C: ComponentTrait> {
f: FnActionWithComponent<C>,
theme_type_id: Option<UniqueId>,
referer_type_id: Option<UniqueId>,
}
/// Filtro para despachar [`FnActionWithComponent`] después de que un tema renderice el componente
/// `C`.
impl<C: ComponentTrait> ActionDispatcher for AfterRender<C> {
/// Devuelve el identificador de tipo ([`UniqueId`]) del tema.
fn theme_type_id(&self) -> Option<UniqueId> {
self.theme_type_id
}
/// Devuelve el identificador de tipo ([`UniqueId`]) del componente `C`.
fn referer_type_id(&self) -> Option<UniqueId> {
self.referer_type_id
}
}
impl<C: ComponentTrait> AfterRender<C> {
/// Permite [registrar](ExtensionTrait::actions) una nueva acción [`FnActionWithComponent`] para
/// un tema dado.
pub fn new(theme: ThemeRef, f: FnActionWithComponent<C>) -> Self {
AfterRender {
f,
theme_type_id: Some(theme.type_id()),
referer_type_id: Some(UniqueId::of::<C>()),
}
}
// Despacha las acciones.
#[inline]
pub(crate) fn dispatch(component: &mut C, cx: &mut Context) {
dispatch_actions(
&ActionKey::new(
UniqueId::of::<Self>(),
Some(cx.theme().type_id()),
Some(UniqueId::of::<C>()),
None,
),
|action: &Self| (action.f)(component, cx),
);
}
}

View file

@ -1,50 +0,0 @@
use crate::prelude::*;
use crate::base::action::FnActionWithComponent;
/// Ejecuta [`FnActionWithComponent`] antes de que un tema renderice el componente.
pub struct BeforeRender<C: ComponentTrait> {
f: FnActionWithComponent<C>,
theme_type_id: Option<UniqueId>,
referer_type_id: Option<UniqueId>,
}
/// Filtro para despachar [`FnActionWithComponent`] antes de que un tema renderice el componente
/// `C`.
impl<C: ComponentTrait> ActionDispatcher for BeforeRender<C> {
/// Devuelve el identificador de tipo ([`UniqueId`]) del tema.
fn theme_type_id(&self) -> Option<UniqueId> {
self.theme_type_id
}
/// Devuelve el identificador de tipo ([`UniqueId`]) del componente `C`.
fn referer_type_id(&self) -> Option<UniqueId> {
self.referer_type_id
}
}
impl<C: ComponentTrait> BeforeRender<C> {
/// Permite [registrar](ExtensionTrait::actions) una nueva acción [`FnActionWithComponent`] para
/// un tema dado.
pub fn new(theme: ThemeRef, f: FnActionWithComponent<C>) -> Self {
BeforeRender {
f,
theme_type_id: Some(theme.type_id()),
referer_type_id: Some(UniqueId::of::<C>()),
}
}
// Despacha las acciones.
#[inline]
pub(crate) fn dispatch(component: &mut C, cx: &mut Context) {
dispatch_actions(
&ActionKey::new(
UniqueId::of::<Self>(),
Some(cx.theme().type_id()),
Some(UniqueId::of::<C>()),
None,
),
|action: &Self| (action.f)(component, cx),
);
}
}

View file

@ -1,63 +0,0 @@
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<C> = 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<C: ComponentTrait> {
f: FnPrepareRender<C>,
theme_type_id: Option<UniqueId>,
referer_type_id: Option<UniqueId>,
}
/// Filtro para despachar [`FnPrepareRender`] que modifica el renderizado de un componente `C`.
impl<C: ComponentTrait> ActionDispatcher for PrepareRender<C> {
/// Devuelve el identificador de tipo ([`UniqueId`]) del tema.
fn theme_type_id(&self) -> Option<UniqueId> {
self.theme_type_id
}
/// Devuelve el identificador de tipo ([`UniqueId`]) del componente `C`.
fn referer_type_id(&self) -> Option<UniqueId> {
self.referer_type_id
}
}
impl<C: ComponentTrait> PrepareRender<C> {
/// Permite [registrar](ExtensionTrait::actions) una nueva acción [`FnPrepareRender`] para un
/// tema dado.
pub fn new(theme: ThemeRef, f: FnPrepareRender<C>) -> Self {
PrepareRender {
f,
theme_type_id: Some(theme.type_id()),
referer_type_id: Some(UniqueId::of::<C>()),
}
}
// 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::<Self>(),
Some(cx.theme().type_id()),
Some(UniqueId::of::<C>()),
None,
),
|action: &Self| {
if render_component.is_empty() {
render_component = (action.f)(component, cx);
}
},
);
render_component
}
}

View file

@ -1,4 +0,0 @@
//! Componentes nativos proporcionados por `PageTop`.
mod html;
pub use html::Html;

View file

@ -1,28 +0,0 @@
use crate::prelude::*;
/// Componente básico para renderizar directamente código HTML.
#[derive(AutoDefault)]
pub struct Html(Markup);
impl ComponentTrait for Html {
fn new() -> Self {
Html::default()
}
fn prepare_component(&self, _cx: &mut Context) -> PrepareMarkup {
PrepareMarkup::With(html! { (self.0) })
}
}
impl Html {
/// Crear una instancia con el código HTML del argumento.
pub fn with(html: Markup) -> Self {
Html(html)
}
/// Modifica el código HTML de la instancia con el nuevo código del argumento.
pub fn alter_html(&mut self, html: Markup) -> &mut Self {
self.0 = html;
self
}
}

View file

@ -1,4 +1,4 @@
//! Tipos y funciones esenciales para crear acciones, componentes, extensiones y temas.
//! Tipos y funciones esenciales para crear acciones, extensiones y temas.
use std::any::Any;
@ -204,9 +204,6 @@ impl<T: ?Sized + AnyInfo> 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;

View file

@ -1,11 +1,11 @@
//! API para definir acciones que inyectan código en el flujo 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.
//! 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.
mod definition;
pub use definition::{ActionBox, ActionDispatcher, ActionKey};
pub use definition::{ActionBase, ActionBox, ActionKey, ActionTrait};
mod list;
use list::ActionsList;
@ -14,33 +14,49 @@ mod all;
pub(crate) use all::add_action;
pub use all::dispatch_actions;
/// Facilita la implementación del método
/// [`actions()`](crate::core::extension::ExtensionTrait::actions).
/// Crea una lista de acciones para facilitar la implementación del método
/// [`actions`](crate::core::extension::ExtensionTrait#method.actions).
///
/// Evita escribir repetidamente `Box::new(...)` para cada acción de la lista, manteniendo el código
/// más limpio.
/// 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.
///
/// # Ejemplo
/// # Ejemplos
///
/// Puede llamarse sin argumentos para crear un vector vacío:
///
/// ```rust,ignore
/// use pagetop::prelude::*;
/// let my_actions = inject_actions![];
/// ```
///
/// impl ExtensionTrait for MyTheme {
/// 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 {
/// fn actions(&self) -> Vec<ActionBox> {
/// actions_boxed![
/// action::theme::BeforeRender::<Button>::new(&Self, before_render_button),
/// action::theme::PrepareRender::<Error404>::new(&Self, render_error404),
/// inject_actions![
/// CustomizeLoginAction::new(),
/// ModifyHeaderAction::new().with_weight(-5),
/// ]
/// }
/// }
///
/// impl ThemeTrait for MyTheme {}
///
/// fn before_render_button(c: &mut Button, cx: &mut Context) { todo!() }
/// fn render_error404(c: &Error404, cx: &mut Context) -> PrepareMarkup { todo!() }
/// ```
///
/// Así, `PageTop` podrá registrar todas estas acciones durante la inicialización de la extensión y
/// posteriormente despacharlas según corresponda.
#[macro_export]
macro_rules! actions_boxed {
macro_rules! inject_actions {
() => {
Vec::<ActionBox>::new()
};

View file

@ -1,9 +1,7 @@
use crate::core::action::{ActionBox, ActionDispatcher, ActionKey, ActionsList};
use parking_lot::RwLock;
use crate::core::action::{ActionBox, ActionKey, ActionTrait, ActionsList};
use std::collections::HashMap;
use std::sync::LazyLock;
use std::sync::{LazyLock, RwLock};
// ACCIONES ****************************************************************************************
@ -14,19 +12,20 @@ static ACTIONS: LazyLock<RwLock<HashMap<ActionKey, ActionsList>>> =
// Registra una nueva acción en el sistema.
//
// Si ya existen acciones con la misma `ActionKey`, la acción se añade a la misma lista. Si no, se
// crea una nueva lista.
// Si ya existen acciones con la misma `ActionKey`, la acción se añade a la lista existente. Si no,
// se crea una nueva lista.
//
// # Uso típico
//
// Las extensiones llamarán a esta función durante su inicialización para instalar acciones
// personalizadas que modifiquen el comportamiento del *core* o de otros componentes.
// personalizadas que modifiquen el comportamiento del núcleo o de otros componentes.
//
// ```rust,ignore
// add_action(Box::new(MyCustomAction::new()));
// ```
pub fn add_action(action: ActionBox) {
let key = ActionKey::new(
action.type_id(),
action.theme_type_id(),
action.referer_type_id(),
action.referer_id(),
);
let mut actions = ACTIONS.write();
let key = action.key();
let mut actions = ACTIONS.write().unwrap();
if let Some(list) = actions.get_mut(&key) {
list.add(action);
} else {
@ -38,35 +37,30 @@ pub fn add_action(action: ActionBox) {
// DESPLEGAR ACCIONES ******************************************************************************
/// Despacha y ejecuta las funciones asociadas a una [`ActionKey`].
/// Despacha las funciones asociadas a un [`ActionKey`] y las ejecuta.
///
/// Permite recorrer de forma segura y ordenada (por peso) la lista de funciones asociadas a una
/// acción específica.
///
/// # Parámetros genéricos
/// - `A`: Tipo de acción que esperamos procesar. Debe implementar [`ActionDispatcher`].
/// - `F`: Función asociada a cada acción, devuelve un valor de tipo `B`.
/// - `A`: Tipo de acción que esperamos procesar. Debe implementar [`ActionTrait`].
/// - `F`: Función o cierre que recibe cada acción y devuelve un valor de tipo `B`.
///
/// # Ejemplo de uso
/// ```rust,ignore
/// pub(crate) fn dispatch(component: &mut C, cx: &mut Context) {
/// dispatch_actions(
/// &ActionKey::new(
/// UniqueId::of::<Self>(),
/// Some(cx.theme().type_id()),
/// Some(UniqueId::of::<C>()),
/// None,
/// ),
/// |action: &Self| (action.f)(component, cx),
/// );
/// }
/// dispatch_actions::<MyCustomAction, _>(&some_key, |action| {
/// action.do_something();
/// });
/// ```
///
/// Esto permite a PageTop o a otros módulos aplicar lógica específica a las acciones de un contexto
/// determinado, manteniendo la flexibilidad del sistema.
pub fn dispatch_actions<A, B, F>(key: &ActionKey, f: F)
where
A: ActionDispatcher,
A: ActionTrait,
F: FnMut(&A) -> B,
{
if let Some(list) = ACTIONS.read().get(key) {
if let Some(list) = ACTIONS.read().unwrap().get(key) {
list.iter_map(f);
}
}

View file

@ -1,13 +1,23 @@
use crate::core::AnyInfo;
use crate::{UniqueId, Weight};
/// Tipo dinámico para encapsular cualquier acción que implementa [`ActionDispatcher`].
pub type ActionBox = Box<dyn ActionDispatcher>;
/// Tipo dinámico para encapsular cualquier acción que implementa [`ActionTrait`].
pub type ActionBox = Box<dyn ActionTrait>;
/// Clave para registrar las acciones y seleccionar las funciones asociadas.
/// Identifica una acción con una clave que define las condiciones de selección de las funciones
/// asociadas a esa acción.
///
/// Las funciones seleccionadas se van a [despachar](crate::core::action::dispatch_actions) y
/// ejecutar en un punto concreto del flujo de ejecución.
///
/// # Campos
///
/// - `action_type_id`: Tipo de la acción.
/// - `theme_type_id`: Opcional, filtra las funciones para un tema dado.
/// - `referer_type_id`: Opcional, filtra las funciones para un tipo dado (p.ej. para un tipo de
/// componente).
/// - `referer_id`: Opcional, filtra las funciones por el identificador de una instancia (p.ej. para
/// un formulario concreto).
#[derive(Eq, PartialEq, Hash)]
pub struct ActionKey {
action_type_id: UniqueId,
@ -19,15 +29,8 @@ pub struct ActionKey {
impl ActionKey {
/// Crea una nueva clave para un tipo de acción.
///
/// Se crea con los siguientes campos:
///
/// - `action_type_id`: Tipo de la acción.
/// - `theme_type_id`: Opcional, identificador de tipo ([`UniqueId`]) del tema asociado.
/// - `referer_type_id`: Opcional, identificador de tipo ([`UniqueId`]) del componente referido.
/// - `referer_id`: Opcional, identificador de la instancia (p.ej. para un formulario concreto).
///
/// Esta clave permitirá seleccionar las funciones a ejecutar para ese tipo de acción, con
/// filtros opcionales por tema, componente, o una instancia concreta según su identificador.
/// Esta clave permite seleccionar las funciones a ejecutar para ese tipo de acción con filtros
/// opcionales por tema, un tipo de referencia, o una instancia concreta según su identificador.
pub fn new(
action_type_id: UniqueId,
theme_type_id: Option<UniqueId>,
@ -43,28 +46,57 @@ impl ActionKey {
}
}
/// Implementa el filtro predeterminado para despachar las funciones de una acción dada.
/// Trait base que permite obtener la clave ([`ActionKey`]) asociada a una acción.
///
/// Las acciones tienen que sobrescribir los métodos para el filtro que apliquen. Por defecto
/// implementa un filtro nulo.
pub trait ActionDispatcher: AnyInfo + Send + Sync {
/// Identificador de tipo ([`UniqueId`]) del tema asociado. En este caso devuelve `None`.
/// Implementado automáticamente para cualquier tipo que cumpla [`ActionTrait`].
pub trait ActionBase {
fn key(&self) -> ActionKey;
}
/// Interfaz común que deben implementar las acciones del código que pueden ser modificadas.
///
/// Este trait combina:
/// - [`AnyInfo`] para identificación única del tipo en tiempo de ejecución.
/// - `Send + Sync` para permitir uso concurrente seguro.
///
/// # Métodos personalizables
/// - `theme_type_id()`: Asocia la acción a un tipo concreto de tema (si aplica).
/// - `referer_type_id()`: Asocia la acción a un tipo de objeto referente (si aplica).
/// - `referer_id()`: Asocia la acción a un identificador concreto.
/// - `weight()`: Controla el orden de aplicación de acciones; valores más bajos se ejecutan antes.
pub trait ActionTrait: ActionBase + AnyInfo + Send + Sync {
/// Especifica el tipo de tema asociado. Por defecto `None`.
fn theme_type_id(&self) -> Option<UniqueId> {
None
}
/// Identificador de tipo ([`UniqueId`]) del objeto referido. En este caso devuelve `None`.
/// Especifica el tipo del objeto referente. Por defecto `None`.
fn referer_type_id(&self) -> Option<UniqueId> {
None
}
/// Identificador del objeto referido. En este caso devuelve `None`.
/// Especifica un identificador único del objeto referente. Por defecto `None`.
fn referer_id(&self) -> Option<String> {
None
}
/// Funciones con pesos más bajos se aplican antes. En este caso siempre devuelve `0`.
/// Define el peso lógico de la acción para determinar el orden de aplicación.
///
/// Acciones con pesos más bajos se aplicarán antes. Se pueden usar valores negativos. Por
/// defecto es `0`.
fn weight(&self) -> Weight {
0
}
}
// Implementación automática que construye la clave `ActionKey` a partir de los métodos definidos.
impl<A: ActionTrait> ActionBase for A {
fn key(&self) -> ActionKey {
ActionKey {
action_type_id: self.type_id(),
theme_type_id: self.theme_type_id(),
referer_type_id: self.referer_type_id(),
referer_id: self.referer_id(),
}
}
}

View file

@ -1,9 +1,9 @@
use crate::core::action::{ActionBox, ActionDispatcher};
use crate::core::action::{ActionBox, ActionTrait};
use crate::core::AnyCast;
use crate::trace;
use crate::AutoDefault;
use parking_lot::RwLock;
use std::sync::RwLock;
#[derive(AutoDefault)]
pub struct ActionsList(RwLock<Vec<ActionBox>>);
@ -14,7 +14,7 @@ impl ActionsList {
}
pub fn add(&mut self, action: ActionBox) {
let mut list = self.0.write();
let mut list = self.0.write().unwrap();
list.push(action);
list.sort_by_key(|a| a.weight());
}
@ -22,12 +22,13 @@ impl ActionsList {
pub fn iter_map<A, B, F>(&self, mut f: F)
where
Self: Sized,
A: ActionDispatcher,
A: ActionTrait,
F: FnMut(&A) -> B,
{
let _: Vec<_> = self
.0
.read()
.unwrap()
.iter()
.rev()
.map(|a| {

View file

@ -1,9 +0,0 @@
//! API para construir nuevos componentes.
mod definition;
pub use definition::{ComponentRender, ComponentTrait};
mod children;
pub use children::Children;
pub use children::{Child, ChildOp};
pub use children::{Typed, TypedOp};

View file

@ -1,327 +0,0 @@
use crate::core::component::ComponentTrait;
use crate::html::{html, Context, Markup};
use crate::{builder_fn, UniqueId};
use parking_lot::RwLock;
use std::sync::Arc;
use std::vec::IntoIter;
/// Representa un componente encapsulado de forma segura y compartida.
///
/// Esta estructura permite manipular y renderizar cualquier tipo que implemente [`ComponentTrait`],
/// garantizando acceso concurrente a través de [`Arc<RwLock<_>>`].
#[derive(Clone)]
pub struct Child(Arc<RwLock<dyn ComponentTrait>>);
impl Child {
/// Crea un nuevo [`Child`] a partir de un componente.
pub fn with(component: impl ComponentTrait) -> Self {
Child(Arc::new(RwLock::new(component)))
}
// Child GETTERS *******************************************************************************
/// Devuelve el identificador del componente, si está definido.
pub fn id(&self) -> Option<String> {
self.0.read().id()
}
// Child RENDER ********************************************************************************
/// Renderiza el componente con el contexto proporcionado.
pub fn render(&self, cx: &mut Context) -> Markup {
self.0.write().render(cx)
}
// Child HELPERS *******************************************************************************
// Devuelve el [`UniqueId`] del tipo del componente.
fn type_id(&self) -> UniqueId {
self.0.read().type_id()
}
}
// *************************************************************************************************
/// Variante tipada de [`Child`] para evitar conversiones durante el uso.
///
/// Facilita el acceso a componentes del mismo tipo sin necesidad de hacer `downcast`.
pub struct Typed<C: ComponentTrait>(Arc<RwLock<C>>);
impl<C: ComponentTrait> Clone for Typed<C> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<C: ComponentTrait> Typed<C> {
/// Crea un nuevo [`Typed`] a partir de un componente.
pub fn with(component: C) -> Self {
Typed(Arc::new(RwLock::new(component)))
}
// Typed GETTERS *******************************************************************************
/// Devuelve el identificador del componente, si está definido.
pub fn id(&self) -> Option<String> {
self.0.read().id()
}
// Typed RENDER ********************************************************************************
/// Renderiza el componente con el contexto proporcionado.
pub fn render(&self, cx: &mut Context) -> Markup {
self.0.write().render(cx)
}
// Typed HELPERS *******************************************************************************
/// Convierte el componente tipado en un [`Child`].
fn to_child(&self) -> Child {
Child(self.0.clone())
}
}
// *************************************************************************************************
/// Operaciones con un componente [`Child`] en una lista [`Children`].
pub enum ChildOp {
Add(Child),
InsertAfterId(&'static str, Child),
InsertBeforeId(&'static str, Child),
Prepend(Child),
RemoveById(&'static str),
ReplaceById(&'static str, Child),
Reset,
}
/// Operaciones con un componente tipado [`Typed<C>`] en una lista [`Children`].
pub enum TypedOp<C: ComponentTrait> {
Add(Typed<C>),
InsertAfterId(&'static str, Typed<C>),
InsertBeforeId(&'static str, Typed<C>),
Prepend(Typed<C>),
RemoveById(&'static str),
ReplaceById(&'static str, Typed<C>),
Reset,
}
/// Lista ordenada de los componentes hijo ([`Child`]) asociados a un componente padre.
///
/// Esta colección permite añadir, modificar, renderizar y consultar componentes hijos en orden de
/// inserción, soportando operaciones avanzadas como inserción relativa o reemplazo por
/// identificador.
#[derive(Clone, Default)]
pub struct Children(Vec<Child>);
impl Children {
/// Crea una lista vacía.
pub fn new() -> Self {
Children::default()
}
/// Crea una lista con un único hijo inicial.
pub fn with(child: Child) -> Self {
Children::default().with_child(ChildOp::Add(child))
}
// Fusiona varias listas de `Children` en una sola.
pub(crate) fn merge(mixes: &[Option<&Children>]) -> Self {
let mut opt = Children::default();
for m in mixes.iter().flatten() {
opt.0.extend(m.0.iter().cloned());
}
opt
}
// Children BUILDER ****************************************************************************
/// Ejecuta una operación con [`ChildOp`] en la lista.
#[builder_fn]
pub fn with_child(mut self, op: ChildOp) -> Self {
match op {
ChildOp::Add(any) => self.add(any),
ChildOp::InsertAfterId(id, any) => self.insert_after_id(id, any),
ChildOp::InsertBeforeId(id, any) => self.insert_before_id(id, any),
ChildOp::Prepend(any) => self.prepend(any),
ChildOp::RemoveById(id) => self.remove_by_id(id),
ChildOp::ReplaceById(id, any) => self.replace_by_id(id, any),
ChildOp::Reset => self.reset(),
}
}
/// Ejecuta una operación con [`TypedOp`] en la lista.
#[builder_fn]
pub fn with_typed<C: ComponentTrait + Default>(mut self, op: TypedOp<C>) -> Self {
match op {
TypedOp::Add(typed) => self.add(typed.to_child()),
TypedOp::InsertAfterId(id, typed) => self.insert_after_id(id, typed.to_child()),
TypedOp::InsertBeforeId(id, typed) => self.insert_before_id(id, typed.to_child()),
TypedOp::Prepend(typed) => self.prepend(typed.to_child()),
TypedOp::RemoveById(id) => self.remove_by_id(id),
TypedOp::ReplaceById(id, typed) => self.replace_by_id(id, typed.to_child()),
TypedOp::Reset => self.reset(),
}
}
/// Añade un hijo al final de la lista.
#[inline]
pub fn add(&mut self, child: Child) -> &mut Self {
self.0.push(child);
self
}
// Inserta un hijo después del componente con el `id` dado, o al final si no se encuentra.
#[inline]
fn insert_after_id(&mut self, id: impl AsRef<str>, child: Child) -> &mut Self {
let id = Some(id.as_ref());
match self.0.iter().position(|c| c.id().as_deref() == id) {
Some(index) => self.0.insert(index + 1, child),
_ => self.0.push(child),
};
self
}
// Inserta un hijo antes del componente con el `id` dado, o al principio si no se encuentra.
#[inline]
fn insert_before_id(&mut self, id: impl AsRef<str>, child: Child) -> &mut Self {
let id = Some(id.as_ref());
match self.0.iter().position(|c| c.id().as_deref() == id) {
Some(index) => self.0.insert(index, child),
_ => self.0.insert(0, child),
};
self
}
// Inserta un hijo al principio de la colección.
#[inline]
fn prepend(&mut self, child: Child) -> &mut Self {
self.0.insert(0, child);
self
}
// Elimina el primer hijo con el `id` dado.
#[inline]
fn remove_by_id(&mut self, id: impl AsRef<str>) -> &mut Self {
let id = Some(id.as_ref());
if let Some(index) = self.0.iter().position(|c| c.id().as_deref() == id) {
self.0.remove(index);
}
self
}
// Sustituye el primer hijo con el `id` dado por otro componente.
#[inline]
fn replace_by_id(&mut self, id: impl AsRef<str>, child: Child) -> &mut Self {
let id = Some(id.as_ref());
for c in &mut self.0 {
if c.id().as_deref() == id {
*c = child;
break;
}
}
self
}
// Elimina todos los componentes hijo de la lista.
#[inline]
fn reset(&mut self) -> &mut Self {
self.0.clear();
self
}
// Children GETTERS ****************************************************************************
/// Devuelve el número de componentes hijo de la lista.
pub fn len(&self) -> usize {
self.0.len()
}
/// Indica si la lista está vacía.
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
/// Devuelve el primer componente hijo con el identificador indicado, si existe.
pub fn get_by_id(&self, id: impl AsRef<str>) -> Option<&Child> {
let id = Some(id.as_ref());
self.0.iter().find(|c| c.id().as_deref() == id)
}
/// Devuelve un iterador sobre los componentes hijo con el identificador indicado.
pub fn iter_by_id<'a>(&'a self, id: &'a str) -> impl Iterator<Item = &'a Child> + 'a {
self.0.iter().filter(move |c| c.id().as_deref() == Some(id))
}
/// Devuelve un iterador sobre los componentes hijo con el identificador tipo ([`UniqueId`])
/// indicado.
pub fn iter_by_type_id(&self, type_id: UniqueId) -> impl Iterator<Item = &Child> {
self.0.iter().filter(move |&c| c.type_id() == type_id)
}
// Children RENDER *****************************************************************************
/// Renderiza todos los componentes hijo, en orden.
pub fn render(&self, cx: &mut Context) -> Markup {
html! {
@for c in &self.0 {
(c.render(cx))
}
}
}
}
impl IntoIterator for Children {
type Item = Child;
type IntoIter = IntoIter<Child>;
/// Consume la estructura `Children`, devolviendo un iterador que consume los elementos.
///
/// ### Ejemplo de uso:
/// ```rust#ignore
/// let children = Children::new().with(child1).with(child2);
/// for child in children {
/// println!("{:?}", child.id());
/// }
/// ```
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl<'a> IntoIterator for &'a Children {
type Item = &'a Child;
type IntoIter = std::slice::Iter<'a, Child>;
/// Itera sobre una referencia inmutable de `Children`, devolviendo un iterador de referencia.
///
/// ### Ejemplo de uso:
/// ```rust#ignore
/// let children = Children::new().with(child1).with(child2);
/// for child in &children {
/// println!("{:?}", child.id());
/// }
/// ```
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}
impl<'a> IntoIterator for &'a mut Children {
type Item = &'a mut Child;
type IntoIter = std::slice::IterMut<'a, Child>;
/// Itera sobre una referencia mutable de `Children`, devolviendo un iterador mutable.
///
/// ### Ejemplo de uso:
/// ```rust#ignore
/// let mut children = Children::new().with(child1).with(child2);
/// for child in &mut children {
/// child.render(&mut context);
/// }
/// ```
fn into_iter(self) -> Self::IntoIter {
self.0.iter_mut()
}
}

View file

@ -1,119 +0,0 @@
use crate::base::action;
use crate::core::{AnyInfo, TypeInfo};
use crate::html::{html, Context, Markup, PrepareMarkup, Render};
/// Define la función de renderizado para todos los componentes.
///
/// Este *trait* se implementa automáticamente en cualquier tipo (componente) que implemente
/// [`ComponentTrait`], por lo que no requiere ninguna codificación manual.
pub trait ComponentRender {
/// Renderiza el componente usando el contexto proporcionado.
fn render(&mut self, cx: &mut Context) -> Markup;
}
/// Interfaz común que debe implementar un componente renderizable en `PageTop`.
///
/// Se recomienda que los componentes deriven [`AutoDefault`](crate::AutoDefault). También deben
/// implementar explícitamente el método [`new`](Self::new) y pueden sobrescribir los otros métodos
/// para personalizar su comportamiento.
pub trait ComponentTrait: AnyInfo + ComponentRender + Send + Sync {
/// Crea una nueva instancia del componente.
fn new() -> Self
where
Self: Sized;
/// Devuelve el nombre del componente.
///
/// Por defecto se obtiene del nombre corto del tipo usando [`TypeInfo::ShortName`].
fn name(&self) -> &'static str {
TypeInfo::ShortName.of::<Self>()
}
/// Devuelve una descripción opcional del componente.
///
/// Por defecto, no se proporciona ninguna descripción (`None`).
fn description(&self) -> Option<String> {
None
}
/// Devuelve un identificador opcional para el componente.
///
/// Este identificador puede usarse para referenciar el componente en el HTML. Por defecto, no
/// tiene ningún identificador (`None`).
fn id(&self) -> Option<String> {
None
}
/// Configura el componente justo antes de preparar el renderizado.
///
/// Este método puede sobrescribirse para modificar la estructura interna del componente o el
/// contexto antes de preparar la renderización del componente. Por defecto no hace nada.
#[allow(unused_variables)]
fn setup_before_prepare(&mut self, cx: &mut Context) {}
/// Devuelve una representación estructurada del componente lista para renderizar.
///
/// Puede sobrescribirse para generar dinámicamente el contenido HTML. Por defecto, devuelve
/// [`PrepareMarkup::None`].
#[allow(unused_variables)]
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
PrepareMarkup::None
}
}
/// Implementa [`render()`](ComponentRender::render) para todos los componentes.
///
/// Y para cada componente ejecuta la siguiente secuencia:
///
/// 1. Despacha [`action::component::IsRenderable`](crate::base::action::component::IsRenderable)
/// para ver si se puede renderizar. Si no es así, devuelve un [`Markup`] vacío.
/// 2. Ejecuta [`setup_before_prepare()`](ComponentTrait::setup_before_prepare) para que el
/// componente pueda ajustar su estructura interna o modificar el contexto.
/// 3. Despacha [`action::theme::BeforeRender<C>`](crate::base::action::theme::BeforeRender) para
/// que el tema pueda hacer ajustes en el componente o el contexto.
/// 4. Despacha [`action::component::BeforeRender<C>`](crate::base::action::component::BeforeRender)
/// para que otras extensiones puedan hacer ajustes.
/// 5. **Prepara el renderizado del componente**:
/// - Despacha [`action::theme::PrepareRender<C>`](crate::base::action::theme::PrepareRender)
/// para permitir al tema preparar un renderizado diferente al predefinido.
/// - Si no es así, ejecuta [`prepare_component()`](ComponentTrait::prepare_component) para
/// preparar el renderizado predefinido del componente.
/// 6. Despacha [`action::theme::AfterRender<C>`](crate::base::action::theme::AfterRender) para
/// que el tema pueda hacer sus últimos ajustes.
/// 7. Despacha [`action::component::AfterRender<C>`](crate::base::action::component::AfterRender)
/// para que otras extensiones puedan hacer sus últimos ajustes.
/// 8. Finalmente devuelve un [`Markup`] del renderizado preparado en el paso 5.
impl<C: ComponentTrait> ComponentRender for C {
fn render(&mut self, cx: &mut Context) -> Markup {
// Si no es renderizable, devuelve un bloque HTML vacío.
if !action::component::IsRenderable::dispatch(self, cx) {
return html! {};
}
// Configura el componente antes de preparar.
self.setup_before_prepare(cx);
// Acciones específicas del tema antes de renderizar el componente.
action::theme::BeforeRender::dispatch(self, cx);
// Acciones de las extensiones antes de renderizar el componente.
action::component::BeforeRender::dispatch(self, cx);
// Prepara el renderizado del componente.
let prepare = action::theme::PrepareRender::dispatch(self, cx);
let prepare = if prepare.is_empty() {
self.prepare_component(cx)
} else {
prepare
};
// Acciones específicas del tema después de renderizar el componente.
action::theme::AfterRender::dispatch(self, cx);
// Acciones de las extensiones después de renderizar el componente.
action::component::AfterRender::dispatch(self, cx);
// Devuelve el marcado final.
prepare.render()
}
}

View file

@ -1,11 +1,9 @@
use crate::core::action::add_action;
use crate::core::extension::ExtensionRef;
use crate::core::theme::all::THEMES;
use crate::{/*global, include_files, include_files_service, */ service, trace};
use crate::{service, trace};
use parking_lot::RwLock;
use std::sync::LazyLock;
use std::sync::{LazyLock, RwLock};
// EXTENSIONES *************************************************************************************
@ -29,11 +27,11 @@ pub fn register_extensions(root_extension: Option<ExtensionRef>) {
add_to_enabled(&mut enabled_list, extension);
}
/* Añade la página de bienvenida por defecto a la lista de extensiones habilitadas.
add_to_enabled(&mut enabled_list, &crate::base::extension::Welcome); */
// Guarda la lista final de extensiones habilitadas.
ENABLED_EXTENSIONS.write().append(&mut enabled_list);
ENABLED_EXTENSIONS
.write()
.unwrap()
.append(&mut enabled_list);
// Prepara una lista de extensiones deshabilitadas.
let mut dropped_list: Vec<ExtensionRef> = Vec::new();
@ -44,7 +42,10 @@ pub fn register_extensions(root_extension: Option<ExtensionRef>) {
}
// Guarda la lista final de extensiones deshabilitadas.
DROPPED_EXTENSIONS.write().append(&mut dropped_list);
DROPPED_EXTENSIONS
.write()
.unwrap()
.append(&mut dropped_list);
}
fn add_to_enabled(list: &mut Vec<ExtensionRef>, extension: ExtensionRef) {
@ -60,7 +61,7 @@ fn add_to_enabled(list: &mut Vec<ExtensionRef>, extension: ExtensionRef) {
// Comprueba si la extensión tiene un tema asociado que deba registrarse.
if let Some(theme) = extension.theme() {
let mut registered_themes = THEMES.write();
let mut registered_themes = THEMES.write().unwrap();
// Asegura que el tema no esté ya registrado para evitar duplicados.
if !registered_themes
.iter()
@ -83,6 +84,7 @@ fn add_to_dropped(list: &mut Vec<ExtensionRef>, extension: ExtensionRef) {
// Comprueba si la extensión está habilitada. Si es así, registra una advertencia.
if ENABLED_EXTENSIONS
.read()
.unwrap()
.iter()
.any(|e| e.type_id() == extension.type_id())
{
@ -107,7 +109,7 @@ fn add_to_dropped(list: &mut Vec<ExtensionRef>, extension: ExtensionRef) {
// REGISTRO DE LAS ACCIONES ************************************************************************
pub fn register_actions() {
for extension in ENABLED_EXTENSIONS.read().iter() {
for extension in ENABLED_EXTENSIONS.read().unwrap().iter() {
for a in extension.actions().into_iter() {
add_action(a);
}
@ -118,20 +120,15 @@ pub fn register_actions() {
pub fn initialize_extensions() {
trace::info!("Calling application bootstrap");
for extension in ENABLED_EXTENSIONS.read().iter() {
for extension in ENABLED_EXTENSIONS.read().unwrap().iter() {
extension.initialize();
}
}
// CONFIGURA LOS SERVICIOS *************************************************************************
//include_files!(assets);
pub fn configure_services(scfg: &mut service::web::ServiceConfig) {
for extension in ENABLED_EXTENSIONS.read().iter() {
for extension in ENABLED_EXTENSIONS.read().unwrap().iter() {
extension.configure_service(scfg);
}
/*include_files_service!(
scfg, assets => "/", [&global::SETTINGS.dev.pagetop_project_dir, "static"]
);*/
}

View file

@ -2,7 +2,7 @@ use crate::core::action::ActionBox;
use crate::core::theme::ThemeRef;
use crate::core::AnyInfo;
use crate::locale::L10n;
use crate::{actions_boxed, service};
use crate::{inject_actions, service};
/// Representa una referencia a una extensión.
///
@ -77,7 +77,7 @@ pub trait ExtensionTrait: AnyInfo + Send + Sync {
/// [peso](crate::Weight), permitiendo personalizar el comportamiento de la aplicación en puntos
/// específicos.
fn actions(&self) -> Vec<ActionBox> {
actions_boxed![]
inject_actions![]
}
/// Inicializa la extensión durante la lógica de arranque de la aplicación.

View file

@ -1,9 +1,7 @@
use crate::core::theme::ThemeRef;
use crate::global;
use parking_lot::RwLock;
use std::sync::LazyLock;
use std::sync::{LazyLock, RwLock};
// TEMAS *******************************************************************************************
@ -19,11 +17,11 @@ pub static DEFAULT_THEME: LazyLock<ThemeRef> =
// TEMA POR NOMBRE *********************************************************************************
/// Devuelve el tema identificado por su [`short_name`](AnyInfo::short_name).
pub fn theme_by_short_name(short_name: impl AsRef<str>) -> Option<ThemeRef> {
let short_name = short_name.as_ref().to_lowercase();
match THEMES
.read()
.unwrap()
.iter()
.find(|t| t.short_name().to_lowercase() == short_name)
{

View file

@ -9,7 +9,7 @@ pub type ThemeRef = &'static dyn ThemeTrait;
/// Interfaz común que debe implementar cualquier tema de `PageTop`.
///
/// Un tema implementará [`ThemeTrait`] y los métodos que sean necesarios de [`ExtensionTrait`],
/// aunque el único obligatorio es [`theme()`](ExtensionTrait::theme).
/// aunque el único obligatorio es [`theme()`](crate::core::extension::ExtensionTrait#method.theme).
///
/// ```rust
/// use pagetop::prelude::*;

View file

@ -27,9 +27,6 @@ pub use opt_translated::OptionTranslated;
mod opt_classes;
pub use opt_classes::{ClassesOp, OptionClasses};
mod opt_component;
pub use opt_component::OptionComponent;
use crate::AutoDefault;
/// Prepara contenido HTML para su conversión a [`Markup`].

View file

@ -1,68 +0,0 @@
use crate::builder_fn;
use crate::core::component::{ComponentTrait, Typed};
use crate::html::{html, Context, Markup};
/// Contenedor de componente para incluir en otros componentes.
///
/// Este tipo encapsula `Option<Typed<C>>` para incluir un componente de manera segura en otros
/// componentes, útil para representar estructuras complejas.
///
/// # Ejemplo
///
/// ```rust,ignore
/// use pagetop::prelude::*;
///
/// let comp = MyComponent::new();
/// let opt = OptionComponent::new(comp);
/// assert!(opt.get().is_some());
/// ```
pub struct OptionComponent<C: ComponentTrait>(Option<Typed<C>>);
impl<C: ComponentTrait> Default for OptionComponent<C> {
fn default() -> Self {
OptionComponent(None)
}
}
impl<C: ComponentTrait> OptionComponent<C> {
/// Crea un nuevo [`OptionComponent`].
///
/// El componente se envuelve automáticamente en un [`Typed`] y se almacena.
pub fn new(component: C) -> Self {
OptionComponent::default().with_value(Some(component))
}
// OptionComponent BUILDER *********************************************************************
/// Establece un componente nuevo, o lo vacía.
///
/// Si se proporciona `Some(component)`, se guarda en [`Typed`]; y si es `None`, se limpia.
#[builder_fn]
pub fn with_value(mut self, component: Option<C>) -> Self {
if let Some(component) = component {
self.0 = Some(Typed::with(component));
} else {
self.0 = None;
}
self
}
// OptionComponent GETTERS *********************************************************************
/// Devuelve el componente, si existe.
pub fn get(&self) -> Option<Typed<C>> {
if let Some(value) = &self.0 {
return Some(value.clone());
}
None
}
/// Renderiza el componente, si existe.
pub fn render(&self, cx: &mut Context) -> Markup {
if let Some(component) = &self.0 {
component.render(cx)
} else {
html! {}
}
}
}

View file

@ -92,11 +92,11 @@ pub mod html;
pub mod locale;
// Soporte a fechas y horas.
pub mod datetime;
// Tipos y funciones esenciales para crear acciones, componentes, extensiones y temas.
// Tipos y funciones esenciales para crear acciones, extensiones y temas.
pub mod core;
// Gestión del servidor y servicios web.
pub mod service;
// Reúne acciones, componentes y temas listos para usar.
// Reúne temas listos para usar.
pub mod base;
// Prepara y ejecuta la aplicación.
pub mod app;

View file

@ -17,7 +17,7 @@ pub use crate::include_locales;
// crate::service
pub use crate::{include_files, include_files_service};
// crate::core::action
pub use crate::actions_boxed;
pub use crate::inject_actions;
// API.
@ -38,12 +38,9 @@ pub use crate::service;
pub use crate::core::{AnyCast, AnyInfo, TypeInfo};
pub use crate::core::action::*;
pub use crate::core::component::*;
pub use crate::core::extension::*;
pub use crate::core::theme::*;
pub use crate::base::action;
pub use crate::base::component::*;
pub use crate::base::theme;
pub use crate::app::Application;