✨ Añade acciones base y renderizado de componentes
- Añade acciones BeforeRender y AfterRender para ejecutar código personalizado antes y después de renderizar un componente. - Introduce la acción PrepareRender para personalizar totalmente el renderizado de un componente. - Se actualizan las definiciones de acciones para utilizar el nuevo "trait" ActionDispatcher. - Se crea un nuevo trait ComponentTrait para definir componentes renderizables. - Se implementan las estructuras Children y Child para gestionar componentes hijos dentro de un componente padre. - Se añade OptionComponent para encapsular de forma segura componentes opcionales y poder usarlos en otros componentes.
This commit is contained in:
parent
f76a208520
commit
37df2ada75
28 changed files with 1102 additions and 147 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -1413,7 +1413,7 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pagetop"
|
name = "pagetop"
|
||||||
version = "0.0.11"
|
version = "0.0.12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-files",
|
"actix-files",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
|
@ -1426,6 +1426,7 @@ dependencies = [
|
||||||
"fluent-templates",
|
"fluent-templates",
|
||||||
"itoa",
|
"itoa",
|
||||||
"pagetop-macros",
|
"pagetop-macros",
|
||||||
|
"parking_lot",
|
||||||
"pastey",
|
"pastey",
|
||||||
"serde",
|
"serde",
|
||||||
"static-files",
|
"static-files",
|
||||||
|
@ -2212,9 +2213,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-actix-web"
|
name = "tracing-actix-web"
|
||||||
version = "0.7.18"
|
version = "0.7.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2340b7722695166c7fc9b3e3cd1166e7c74fedb9075b8f0c74d3822d2e41caf5"
|
checksum = "5360edd490ec8dee9fedfc6a9fd83ac2f01b3e1996e3261b9ad18a61971fe064"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"mutually_exclusive_features",
|
"mutually_exclusive_features",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "pagetop"
|
name = "pagetop"
|
||||||
version = "0.0.11"
|
version = "0.0.12"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
description = """\
|
description = """\
|
||||||
|
@ -21,6 +21,7 @@ concat-string = "1.0.1"
|
||||||
config = { version = "0.15.13", default-features = false, features = ["toml"] }
|
config = { version = "0.15.13", default-features = false, features = ["toml"] }
|
||||||
figlet-rs = "0.1.5"
|
figlet-rs = "0.1.5"
|
||||||
itoa = "1.0.15"
|
itoa = "1.0.15"
|
||||||
|
parking_lot = "0.12.4"
|
||||||
paste = { package = "pastey", version = "0.1.0" }
|
paste = { package = "pastey", version = "0.1.0" }
|
||||||
substring = "1.4.5"
|
substring = "1.4.5"
|
||||||
terminal_size = "0.4.2"
|
terminal_size = "0.4.2"
|
||||||
|
@ -28,7 +29,7 @@ terminal_size = "0.4.2"
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.41"
|
||||||
tracing-appender = "0.2.3"
|
tracing-appender = "0.2.3"
|
||||||
tracing-subscriber = { version = "0.3.19", features = ["json", "env-filter"] }
|
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"
|
fluent-templates = "0.13.0"
|
||||||
unic-langid = { version = "0.9.6", features = ["macros"] }
|
unic-langid = { version = "0.9.6", features = ["macros"] }
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
//! Reúne temas listos para usar.
|
//! Reúne acciones y temas listos para usar.
|
||||||
|
|
||||||
|
pub mod action;
|
||||||
|
|
||||||
pub mod theme;
|
pub mod theme;
|
||||||
|
|
15
src/base/action.rs
Normal file
15
src/base/action.rs
Normal file
|
@ -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<C> = fn(component: &mut C, cx: &mut Context);
|
||||||
|
|
||||||
|
pub mod component;
|
||||||
|
|
||||||
|
pub mod theme;
|
10
src/base/action/component.rs
Normal file
10
src/base/action/component.rs
Normal file
|
@ -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::*;
|
81
src/base/action/component/after_render_component.rs
Normal file
81
src/base/action/component/after_render_component.rs
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
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),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
81
src/base/action/component/before_render_component.rs
Normal file
81
src/base/action/component/before_render_component.rs
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
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),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
96
src/base/action/component/is_renderable.rs
Normal file
96
src/base/action/component/is_renderable.rs
Normal file
|
@ -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<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
|
||||||
|
}
|
||||||
|
}
|
10
src/base/action/theme.rs
Normal file
10
src/base/action/theme.rs
Normal file
|
@ -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::*;
|
50
src/base/action/theme/after_render_component.rs
Normal file
50
src/base/action/theme/after_render_component.rs
Normal file
|
@ -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<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),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
50
src/base/action/theme/before_render_component.rs
Normal file
50
src/base/action/theme/before_render_component.rs
Normal file
|
@ -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<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),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
63
src/base/action/theme/prepare_render.rs
Normal file
63
src/base/action/theme/prepare_render.rs
Normal file
|
@ -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<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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
use std::any::Any;
|
||||||
|
|
||||||
|
@ -204,6 +204,9 @@ impl<T: ?Sized + AnyInfo> AnyCast for T {}
|
||||||
// API para definir acciones que alteran el comportamiento predeterminado del código.
|
// API para definir acciones que alteran el comportamiento predeterminado del código.
|
||||||
pub mod action;
|
pub mod action;
|
||||||
|
|
||||||
|
// API para construir nuevos componentes.
|
||||||
|
pub mod component;
|
||||||
|
|
||||||
// API para añadir nuevas funcionalidades usando extensiones.
|
// API para añadir nuevas funcionalidades usando extensiones.
|
||||||
pub mod extension;
|
pub mod extension;
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
//! API para definir acciones que inyectan código en el flujo de la aplicación.
|
//! 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
|
//! Permite crear acciones para que otros *crates* puedan inyectar código usando funciones *ad hoc*
|
||||||
//! funciones *ad hoc* que modifican el comportamiento predefinido en puntos concretos del flujo de
|
//! que modifican el comportamiento predefinido en puntos concretos del flujo de ejecución de la
|
||||||
//! ejecución de la aplicación.
|
//! aplicación.
|
||||||
|
|
||||||
mod definition;
|
mod definition;
|
||||||
pub use definition::{ActionBase, ActionBox, ActionKey, ActionTrait};
|
pub use definition::{ActionBox, ActionDispatcher, ActionKey};
|
||||||
|
|
||||||
mod list;
|
mod list;
|
||||||
use list::ActionsList;
|
use list::ActionsList;
|
||||||
|
@ -14,49 +14,33 @@ mod all;
|
||||||
pub(crate) use all::add_action;
|
pub(crate) use all::add_action;
|
||||||
pub use all::dispatch_actions;
|
pub use all::dispatch_actions;
|
||||||
|
|
||||||
/// Crea una lista de acciones para facilitar la implementación del método
|
/// Facilita la implementación del método
|
||||||
/// [`actions`](crate::core::extension::ExtensionTrait#method.actions).
|
/// [`actions()`](crate::core::extension::ExtensionTrait::actions).
|
||||||
///
|
///
|
||||||
/// Esta macro crea vectores de [`ActionBox`], el tipo dinámico que encapsula cualquier acción que
|
/// Evita escribir repetidamente `Box::new(...)` para cada acción de la lista, manteniendo el código
|
||||||
/// implemente [`ActionTrait`]. Evita escribir repetidamente `Box::new(...)` para cada acción
|
/// más limpio.
|
||||||
/// inyectada, manteniendo el código más limpio.
|
|
||||||
///
|
///
|
||||||
/// # Ejemplos
|
/// # Ejemplo
|
||||||
///
|
|
||||||
/// Puede llamarse sin argumentos para crear un vector vacío:
|
|
||||||
///
|
///
|
||||||
/// ```rust,ignore
|
/// ```rust,ignore
|
||||||
/// let my_actions = inject_actions![];
|
/// use pagetop::prelude::*;
|
||||||
/// ```
|
|
||||||
///
|
///
|
||||||
/// O con una lista de acciones concretas:
|
/// impl ExtensionTrait for MyTheme {
|
||||||
///
|
|
||||||
/// ```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> {
|
/// fn actions(&self) -> Vec<ActionBox> {
|
||||||
/// inject_actions![
|
/// actions_boxed![
|
||||||
/// CustomizeLoginAction::new(),
|
/// action::theme::BeforeRender::<Button>::new(&Self, before_render_button),
|
||||||
/// ModifyHeaderAction::new().with_weight(-5),
|
/// action::theme::PrepareRender::<Error404>::new(&Self, render_error404),
|
||||||
/// ]
|
/// ]
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
|
||||||
///
|
///
|
||||||
/// Así, `PageTop` podrá registrar todas estas acciones durante la inicialización de la extensión y
|
/// impl ThemeTrait for MyTheme {}
|
||||||
/// posteriormente despacharlas según corresponda.
|
///
|
||||||
|
/// fn before_render_button(c: &mut Button, cx: &mut Context) { todo!() }
|
||||||
|
/// fn render_error404(c: &Error404, cx: &mut Context) -> PrepareMarkup { todo!() }
|
||||||
|
/// ```
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! inject_actions {
|
macro_rules! actions_boxed {
|
||||||
() => {
|
() => {
|
||||||
Vec::<ActionBox>::new()
|
Vec::<ActionBox>::new()
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
use crate::core::action::{ActionBox, ActionKey, ActionTrait, ActionsList};
|
use crate::core::action::{ActionBox, ActionDispatcher, ActionKey, ActionsList};
|
||||||
|
|
||||||
|
use parking_lot::RwLock;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{LazyLock, RwLock};
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
// ACCIONES ****************************************************************************************
|
// ACCIONES ****************************************************************************************
|
||||||
|
|
||||||
|
@ -12,20 +14,19 @@ static ACTIONS: LazyLock<RwLock<HashMap<ActionKey, ActionsList>>> =
|
||||||
|
|
||||||
// Registra una nueva acción en el sistema.
|
// Registra una nueva acción en el sistema.
|
||||||
//
|
//
|
||||||
// Si ya existen acciones con la misma `ActionKey`, la acción se añade a la lista existente. Si no,
|
// Si ya existen acciones con la misma `ActionKey`, la acción se añade a la misma lista. Si no, se
|
||||||
// se crea una nueva lista.
|
// crea una nueva lista.
|
||||||
//
|
|
||||||
// # Uso típico
|
|
||||||
//
|
//
|
||||||
// Las extensiones llamarán a esta función durante su inicialización para instalar acciones
|
// Las extensiones llamarán a esta función durante su inicialización para instalar acciones
|
||||||
// personalizadas que modifiquen el comportamiento del núcleo o de otros componentes.
|
// personalizadas que modifiquen el comportamiento del *core* o de otros componentes.
|
||||||
//
|
|
||||||
// ```rust,ignore
|
|
||||||
// add_action(Box::new(MyCustomAction::new()));
|
|
||||||
// ```
|
|
||||||
pub fn add_action(action: ActionBox) {
|
pub fn add_action(action: ActionBox) {
|
||||||
let key = action.key();
|
let key = ActionKey::new(
|
||||||
let mut actions = ACTIONS.write().unwrap();
|
action.type_id(),
|
||||||
|
action.theme_type_id(),
|
||||||
|
action.referer_type_id(),
|
||||||
|
action.referer_id(),
|
||||||
|
);
|
||||||
|
let mut actions = ACTIONS.write();
|
||||||
if let Some(list) = actions.get_mut(&key) {
|
if let Some(list) = actions.get_mut(&key) {
|
||||||
list.add(action);
|
list.add(action);
|
||||||
} else {
|
} else {
|
||||||
|
@ -37,30 +38,35 @@ pub fn add_action(action: ActionBox) {
|
||||||
|
|
||||||
// DESPLEGAR ACCIONES ******************************************************************************
|
// DESPLEGAR ACCIONES ******************************************************************************
|
||||||
|
|
||||||
/// Despacha las funciones asociadas a un [`ActionKey`] y las ejecuta.
|
/// Despacha y ejecuta las funciones asociadas a una [`ActionKey`].
|
||||||
///
|
///
|
||||||
/// Permite recorrer de forma segura y ordenada (por peso) la lista de funciones asociadas a una
|
/// Permite recorrer de forma segura y ordenada (por peso) la lista de funciones asociadas a una
|
||||||
/// acción específica.
|
/// acción específica.
|
||||||
///
|
///
|
||||||
/// # Parámetros genéricos
|
/// # Parámetros genéricos
|
||||||
/// - `A`: Tipo de acción que esperamos procesar. Debe implementar [`ActionTrait`].
|
/// - `A`: Tipo de acción que esperamos procesar. Debe implementar [`ActionDispatcher`].
|
||||||
/// - `F`: Función o cierre que recibe cada acción y devuelve un valor de tipo `B`.
|
/// - `F`: Función asociada a cada acción, devuelve un valor de tipo `B`.
|
||||||
///
|
///
|
||||||
/// # Ejemplo de uso
|
/// # Ejemplo de uso
|
||||||
/// ```rust,ignore
|
/// ```rust,ignore
|
||||||
/// dispatch_actions::<MyCustomAction, _>(&some_key, |action| {
|
/// pub(crate) fn dispatch(component: &mut C, cx: &mut Context) {
|
||||||
/// action.do_something();
|
/// dispatch_actions(
|
||||||
/// });
|
/// &ActionKey::new(
|
||||||
|
/// UniqueId::of::<Self>(),
|
||||||
|
/// Some(cx.theme().type_id()),
|
||||||
|
/// Some(UniqueId::of::<C>()),
|
||||||
|
/// None,
|
||||||
|
/// ),
|
||||||
|
/// |action: &Self| (action.f)(component, cx),
|
||||||
|
/// );
|
||||||
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
|
||||||
/// 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)
|
pub fn dispatch_actions<A, B, F>(key: &ActionKey, f: F)
|
||||||
where
|
where
|
||||||
A: ActionTrait,
|
A: ActionDispatcher,
|
||||||
F: FnMut(&A) -> B,
|
F: FnMut(&A) -> B,
|
||||||
{
|
{
|
||||||
if let Some(list) = ACTIONS.read().unwrap().get(key) {
|
if let Some(list) = ACTIONS.read().get(key) {
|
||||||
list.iter_map(f);
|
list.iter_map(f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,13 @@
|
||||||
use crate::core::AnyInfo;
|
use crate::core::AnyInfo;
|
||||||
use crate::{UniqueId, Weight};
|
use crate::{UniqueId, Weight};
|
||||||
|
|
||||||
/// Tipo dinámico para encapsular cualquier acción que implementa [`ActionTrait`].
|
/// Tipo dinámico para encapsular cualquier acción que implementa [`ActionDispatcher`].
|
||||||
pub type ActionBox = Box<dyn ActionTrait>;
|
pub type ActionBox = Box<dyn ActionDispatcher>;
|
||||||
|
|
||||||
/// Identifica una acción con una clave que define las condiciones de selección de las funciones
|
/// Clave para registrar las acciones y seleccionar las funciones asociadas.
|
||||||
/// asociadas a esa acción.
|
|
||||||
///
|
///
|
||||||
/// Las funciones seleccionadas se van a [despachar](crate::core::action::dispatch_actions) y
|
/// Las funciones seleccionadas se van a [despachar](crate::core::action::dispatch_actions) y
|
||||||
/// ejecutar en un punto concreto del flujo de ejecución.
|
/// 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)]
|
#[derive(Eq, PartialEq, Hash)]
|
||||||
pub struct ActionKey {
|
pub struct ActionKey {
|
||||||
action_type_id: UniqueId,
|
action_type_id: UniqueId,
|
||||||
|
@ -29,8 +19,15 @@ pub struct ActionKey {
|
||||||
impl ActionKey {
|
impl ActionKey {
|
||||||
/// Crea una nueva clave para un tipo de acción.
|
/// Crea una nueva clave para un tipo de acción.
|
||||||
///
|
///
|
||||||
/// Esta clave permite seleccionar las funciones a ejecutar para ese tipo de acción con filtros
|
/// Se crea con los siguientes campos:
|
||||||
/// opcionales por tema, un tipo de referencia, o una instancia concreta según su identificador.
|
///
|
||||||
|
/// - `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.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
action_type_id: UniqueId,
|
action_type_id: UniqueId,
|
||||||
theme_type_id: Option<UniqueId>,
|
theme_type_id: Option<UniqueId>,
|
||||||
|
@ -46,57 +43,28 @@ impl ActionKey {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trait base que permite obtener la clave ([`ActionKey`]) asociada a una acción.
|
/// Implementa el filtro predeterminado para despachar las funciones de una acción dada.
|
||||||
///
|
///
|
||||||
/// Implementado automáticamente para cualquier tipo que cumpla [`ActionTrait`].
|
/// Las acciones tienen que sobrescribir los métodos para el filtro que apliquen. Por defecto
|
||||||
pub trait ActionBase {
|
/// implementa un filtro nulo.
|
||||||
fn key(&self) -> ActionKey;
|
pub trait ActionDispatcher: AnyInfo + Send + Sync {
|
||||||
}
|
/// Identificador de tipo ([`UniqueId`]) del tema asociado. En este caso devuelve `None`.
|
||||||
|
|
||||||
/// 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> {
|
fn theme_type_id(&self) -> Option<UniqueId> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Especifica el tipo del objeto referente. Por defecto `None`.
|
/// Identificador de tipo ([`UniqueId`]) del objeto referido. En este caso devuelve `None`.
|
||||||
fn referer_type_id(&self) -> Option<UniqueId> {
|
fn referer_type_id(&self) -> Option<UniqueId> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Especifica un identificador único del objeto referente. Por defecto `None`.
|
/// Identificador del objeto referido. En este caso devuelve `None`.
|
||||||
fn referer_id(&self) -> Option<String> {
|
fn referer_id(&self) -> Option<String> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Define el peso lógico de la acción para determinar el orden de aplicación.
|
/// Funciones con pesos más bajos se aplican antes. En este caso siempre devuelve `0`.
|
||||||
///
|
|
||||||
/// Acciones con pesos más bajos se aplicarán antes. Se pueden usar valores negativos. Por
|
|
||||||
/// defecto es `0`.
|
|
||||||
fn weight(&self) -> Weight {
|
fn weight(&self) -> Weight {
|
||||||
0
|
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(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use crate::core::action::{ActionBox, ActionTrait};
|
use crate::core::action::{ActionBox, ActionDispatcher};
|
||||||
use crate::core::AnyCast;
|
use crate::core::AnyCast;
|
||||||
use crate::trace;
|
use crate::trace;
|
||||||
use crate::AutoDefault;
|
use crate::AutoDefault;
|
||||||
|
|
||||||
use std::sync::RwLock;
|
use parking_lot::RwLock;
|
||||||
|
|
||||||
#[derive(AutoDefault)]
|
#[derive(AutoDefault)]
|
||||||
pub struct ActionsList(RwLock<Vec<ActionBox>>);
|
pub struct ActionsList(RwLock<Vec<ActionBox>>);
|
||||||
|
@ -14,7 +14,7 @@ impl ActionsList {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add(&mut self, action: ActionBox) {
|
pub fn add(&mut self, action: ActionBox) {
|
||||||
let mut list = self.0.write().unwrap();
|
let mut list = self.0.write();
|
||||||
list.push(action);
|
list.push(action);
|
||||||
list.sort_by_key(|a| a.weight());
|
list.sort_by_key(|a| a.weight());
|
||||||
}
|
}
|
||||||
|
@ -22,13 +22,12 @@ impl ActionsList {
|
||||||
pub fn iter_map<A, B, F>(&self, mut f: F)
|
pub fn iter_map<A, B, F>(&self, mut f: F)
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
A: ActionTrait,
|
A: ActionDispatcher,
|
||||||
F: FnMut(&A) -> B,
|
F: FnMut(&A) -> B,
|
||||||
{
|
{
|
||||||
let _: Vec<_> = self
|
let _: Vec<_> = self
|
||||||
.0
|
.0
|
||||||
.read()
|
.read()
|
||||||
.unwrap()
|
|
||||||
.iter()
|
.iter()
|
||||||
.rev()
|
.rev()
|
||||||
.map(|a| {
|
.map(|a| {
|
||||||
|
|
9
src/core/component.rs
Normal file
9
src/core/component.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
//! 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};
|
327
src/core/component/children.rs
Normal file
327
src/core/component/children.rs
Normal file
|
@ -0,0 +1,327 @@
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
121
src/core/component/definition.rs
Normal file
121
src/core/component/definition.rs
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
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 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.
|
||||||
|
///
|
||||||
|
/// Este método debe implementarse explícitamente.
|
||||||
|
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 la 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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,11 @@
|
||||||
use crate::core::action::add_action;
|
use crate::core::action::add_action;
|
||||||
use crate::core::extension::ExtensionRef;
|
use crate::core::extension::ExtensionRef;
|
||||||
use crate::core::theme::all::THEMES;
|
use crate::core::theme::all::THEMES;
|
||||||
use crate::{service, trace};
|
use crate::{/*global, include_files, include_files_service, */ service, trace};
|
||||||
|
|
||||||
use std::sync::{LazyLock, RwLock};
|
use parking_lot::RwLock;
|
||||||
|
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
// EXTENSIONES *************************************************************************************
|
// EXTENSIONES *************************************************************************************
|
||||||
|
|
||||||
|
@ -27,11 +29,11 @@ pub fn register_extensions(root_extension: Option<ExtensionRef>) {
|
||||||
add_to_enabled(&mut enabled_list, extension);
|
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.
|
// Guarda la lista final de extensiones habilitadas.
|
||||||
ENABLED_EXTENSIONS
|
ENABLED_EXTENSIONS.write().append(&mut enabled_list);
|
||||||
.write()
|
|
||||||
.unwrap()
|
|
||||||
.append(&mut enabled_list);
|
|
||||||
|
|
||||||
// Prepara una lista de extensiones deshabilitadas.
|
// Prepara una lista de extensiones deshabilitadas.
|
||||||
let mut dropped_list: Vec<ExtensionRef> = Vec::new();
|
let mut dropped_list: Vec<ExtensionRef> = Vec::new();
|
||||||
|
@ -42,10 +44,7 @@ pub fn register_extensions(root_extension: Option<ExtensionRef>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Guarda la lista final de extensiones deshabilitadas.
|
// Guarda la lista final de extensiones deshabilitadas.
|
||||||
DROPPED_EXTENSIONS
|
DROPPED_EXTENSIONS.write().append(&mut dropped_list);
|
||||||
.write()
|
|
||||||
.unwrap()
|
|
||||||
.append(&mut dropped_list);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_to_enabled(list: &mut Vec<ExtensionRef>, extension: ExtensionRef) {
|
fn add_to_enabled(list: &mut Vec<ExtensionRef>, extension: ExtensionRef) {
|
||||||
|
@ -61,7 +60,7 @@ fn add_to_enabled(list: &mut Vec<ExtensionRef>, extension: ExtensionRef) {
|
||||||
|
|
||||||
// Comprueba si la extensión tiene un tema asociado que deba registrarse.
|
// Comprueba si la extensión tiene un tema asociado que deba registrarse.
|
||||||
if let Some(theme) = extension.theme() {
|
if let Some(theme) = extension.theme() {
|
||||||
let mut registered_themes = THEMES.write().unwrap();
|
let mut registered_themes = THEMES.write();
|
||||||
// Asegura que el tema no esté ya registrado para evitar duplicados.
|
// Asegura que el tema no esté ya registrado para evitar duplicados.
|
||||||
if !registered_themes
|
if !registered_themes
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -84,7 +83,6 @@ fn add_to_dropped(list: &mut Vec<ExtensionRef>, extension: ExtensionRef) {
|
||||||
// Comprueba si la extensión está habilitada. Si es así, registra una advertencia.
|
// Comprueba si la extensión está habilitada. Si es así, registra una advertencia.
|
||||||
if ENABLED_EXTENSIONS
|
if ENABLED_EXTENSIONS
|
||||||
.read()
|
.read()
|
||||||
.unwrap()
|
|
||||||
.iter()
|
.iter()
|
||||||
.any(|e| e.type_id() == extension.type_id())
|
.any(|e| e.type_id() == extension.type_id())
|
||||||
{
|
{
|
||||||
|
@ -109,7 +107,7 @@ fn add_to_dropped(list: &mut Vec<ExtensionRef>, extension: ExtensionRef) {
|
||||||
// REGISTRO DE LAS ACCIONES ************************************************************************
|
// REGISTRO DE LAS ACCIONES ************************************************************************
|
||||||
|
|
||||||
pub fn register_actions() {
|
pub fn register_actions() {
|
||||||
for extension in ENABLED_EXTENSIONS.read().unwrap().iter() {
|
for extension in ENABLED_EXTENSIONS.read().iter() {
|
||||||
for a in extension.actions().into_iter() {
|
for a in extension.actions().into_iter() {
|
||||||
add_action(a);
|
add_action(a);
|
||||||
}
|
}
|
||||||
|
@ -120,15 +118,20 @@ pub fn register_actions() {
|
||||||
|
|
||||||
pub fn initialize_extensions() {
|
pub fn initialize_extensions() {
|
||||||
trace::info!("Calling application bootstrap");
|
trace::info!("Calling application bootstrap");
|
||||||
for extension in ENABLED_EXTENSIONS.read().unwrap().iter() {
|
for extension in ENABLED_EXTENSIONS.read().iter() {
|
||||||
extension.initialize();
|
extension.initialize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CONFIGURA LOS SERVICIOS *************************************************************************
|
// CONFIGURA LOS SERVICIOS *************************************************************************
|
||||||
|
|
||||||
|
//include_files!(assets);
|
||||||
|
|
||||||
pub fn configure_services(scfg: &mut service::web::ServiceConfig) {
|
pub fn configure_services(scfg: &mut service::web::ServiceConfig) {
|
||||||
for extension in ENABLED_EXTENSIONS.read().unwrap().iter() {
|
for extension in ENABLED_EXTENSIONS.read().iter() {
|
||||||
extension.configure_service(scfg);
|
extension.configure_service(scfg);
|
||||||
}
|
}
|
||||||
|
/*include_files_service!(
|
||||||
|
scfg, assets => "/", [&global::SETTINGS.dev.pagetop_project_dir, "static"]
|
||||||
|
);*/
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use crate::core::action::ActionBox;
|
||||||
use crate::core::theme::ThemeRef;
|
use crate::core::theme::ThemeRef;
|
||||||
use crate::core::AnyInfo;
|
use crate::core::AnyInfo;
|
||||||
use crate::locale::L10n;
|
use crate::locale::L10n;
|
||||||
use crate::{inject_actions, service};
|
use crate::{actions_boxed, service};
|
||||||
|
|
||||||
/// Representa una referencia a una extensión.
|
/// 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
|
/// [peso](crate::Weight), permitiendo personalizar el comportamiento de la aplicación en puntos
|
||||||
/// específicos.
|
/// específicos.
|
||||||
fn actions(&self) -> Vec<ActionBox> {
|
fn actions(&self) -> Vec<ActionBox> {
|
||||||
inject_actions![]
|
actions_boxed![]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inicializa la extensión durante la lógica de arranque de la aplicación.
|
/// Inicializa la extensión durante la lógica de arranque de la aplicación.
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
use crate::core::theme::ThemeRef;
|
use crate::core::theme::ThemeRef;
|
||||||
use crate::global;
|
use crate::global;
|
||||||
|
|
||||||
use std::sync::{LazyLock, RwLock};
|
use parking_lot::RwLock;
|
||||||
|
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
// TEMAS *******************************************************************************************
|
// TEMAS *******************************************************************************************
|
||||||
|
|
||||||
|
@ -17,11 +19,11 @@ pub static DEFAULT_THEME: LazyLock<ThemeRef> =
|
||||||
|
|
||||||
// TEMA POR NOMBRE *********************************************************************************
|
// 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> {
|
pub fn theme_by_short_name(short_name: impl AsRef<str>) -> Option<ThemeRef> {
|
||||||
let short_name = short_name.as_ref().to_lowercase();
|
let short_name = short_name.as_ref().to_lowercase();
|
||||||
match THEMES
|
match THEMES
|
||||||
.read()
|
.read()
|
||||||
.unwrap()
|
|
||||||
.iter()
|
.iter()
|
||||||
.find(|t| t.short_name().to_lowercase() == short_name)
|
.find(|t| t.short_name().to_lowercase() == short_name)
|
||||||
{
|
{
|
||||||
|
|
|
@ -9,7 +9,7 @@ pub type ThemeRef = &'static dyn ThemeTrait;
|
||||||
/// Interfaz común que debe implementar cualquier tema de `PageTop`.
|
/// Interfaz común que debe implementar cualquier tema de `PageTop`.
|
||||||
///
|
///
|
||||||
/// Un tema implementará [`ThemeTrait`] y los métodos que sean necesarios de [`ExtensionTrait`],
|
/// Un tema implementará [`ThemeTrait`] y los métodos que sean necesarios de [`ExtensionTrait`],
|
||||||
/// aunque el único obligatorio es [`theme()`](crate::core::extension::ExtensionTrait#method.theme).
|
/// aunque el único obligatorio es [`theme()`](ExtensionTrait::theme).
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use pagetop::prelude::*;
|
/// use pagetop::prelude::*;
|
||||||
|
|
|
@ -27,6 +27,9 @@ pub use opt_translated::OptionTranslated;
|
||||||
mod opt_classes;
|
mod opt_classes;
|
||||||
pub use opt_classes::{ClassesOp, OptionClasses};
|
pub use opt_classes::{ClassesOp, OptionClasses};
|
||||||
|
|
||||||
|
mod opt_component;
|
||||||
|
pub use opt_component::OptionComponent;
|
||||||
|
|
||||||
use crate::AutoDefault;
|
use crate::AutoDefault;
|
||||||
|
|
||||||
/// Prepara contenido HTML para su conversión a [`Markup`].
|
/// Prepara contenido HTML para su conversión a [`Markup`].
|
||||||
|
|
68
src/html/opt_component.rs
Normal file
68
src/html/opt_component.rs
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
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! {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -92,11 +92,11 @@ pub mod html;
|
||||||
pub mod locale;
|
pub mod locale;
|
||||||
// Soporte a fechas y horas.
|
// Soporte a fechas y horas.
|
||||||
pub mod datetime;
|
pub mod datetime;
|
||||||
// Tipos y funciones esenciales para crear acciones, extensiones y temas.
|
// Tipos y funciones esenciales para crear acciones, componentes, extensiones y temas.
|
||||||
pub mod core;
|
pub mod core;
|
||||||
// Gestión del servidor y servicios web.
|
// Gestión del servidor y servicios web.
|
||||||
pub mod service;
|
pub mod service;
|
||||||
// Reúne temas listos para usar.
|
// Reúne acciones y temas listos para usar.
|
||||||
pub mod base;
|
pub mod base;
|
||||||
// Prepara y ejecuta la aplicación.
|
// Prepara y ejecuta la aplicación.
|
||||||
pub mod app;
|
pub mod app;
|
||||||
|
|
|
@ -17,7 +17,7 @@ pub use crate::include_locales;
|
||||||
// crate::service
|
// crate::service
|
||||||
pub use crate::{include_files, include_files_service};
|
pub use crate::{include_files, include_files_service};
|
||||||
// crate::core::action
|
// crate::core::action
|
||||||
pub use crate::inject_actions;
|
pub use crate::actions_boxed;
|
||||||
|
|
||||||
// API.
|
// API.
|
||||||
|
|
||||||
|
@ -38,9 +38,11 @@ pub use crate::service;
|
||||||
pub use crate::core::{AnyCast, AnyInfo, TypeInfo};
|
pub use crate::core::{AnyCast, AnyInfo, TypeInfo};
|
||||||
|
|
||||||
pub use crate::core::action::*;
|
pub use crate::core::action::*;
|
||||||
|
pub use crate::core::component::*;
|
||||||
pub use crate::core::extension::*;
|
pub use crate::core::extension::*;
|
||||||
pub use crate::core::theme::*;
|
pub use crate::core::theme::*;
|
||||||
|
|
||||||
|
pub use crate::base::action;
|
||||||
pub use crate::base::theme;
|
pub use crate::base::theme;
|
||||||
|
|
||||||
pub use crate::app::Application;
|
pub use crate::app::Application;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue