Compare commits
No commits in common. "74dbcde5c5736dc06a51f5cdf3db5f10ffdf3a89" and "b9d9cdf6016738024d50b31321c8761bcd913e51" have entirely different histories.
74dbcde5c5
...
b9d9cdf601
8 changed files with 114 additions and 154 deletions
|
|
@ -381,8 +381,7 @@ pub fn on_htmx(event: &str) -> String {
|
|||
///
|
||||
/// Están en minúsculas porque así las normaliza el módulo `http`. Se pueden usar con
|
||||
/// [`HttpRequest::headers()`](pagetop::web::HttpRequest::headers) para leer sus valores
|
||||
/// directamente, aunque lo habitual es usar el trait
|
||||
/// [`HtmxRequestExt`](crate::request::HtmxRequestExt).
|
||||
/// directamente, aunque lo habitual es usar el trait [`HtmxRequestExt`](crate::HtmxRequestExt).
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use pagetop::prelude::*;
|
||||
|
|
@ -418,8 +417,7 @@ pub mod request {
|
|||
/// Cabeceras de respuesta HTTP para HTMX.
|
||||
///
|
||||
/// Se pueden usar con [`HeaderMap`](pagetop::web::http::HeaderMap) para construir respuestas
|
||||
/// manualmente, aunque lo habitual es usar el constructor
|
||||
/// [`HtmxResponse`](crate::response::HtmxResponse).
|
||||
/// manualmente, aunque lo habitual es usar el constructor [`HtmxResponse`](crate::HtmxResponse).
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use pagetop::prelude::*;
|
||||
|
|
@ -433,38 +431,38 @@ pub mod request {
|
|||
/// ```
|
||||
pub mod response {
|
||||
/// Redirige mediante AJAX a la URL o configuración JSON indicada. Ver
|
||||
/// [`HtmxResponse::location()`](crate::response::HtmxResponse::location) y
|
||||
/// [`HtmxResponse::location_json()`](crate::response::HtmxResponse::location_json).
|
||||
/// [`HtmxResponse::location()`](crate::HtmxResponse::location) y
|
||||
/// [`HtmxResponse::location_json()`](crate::HtmxResponse::location_json).
|
||||
pub const LOCATION: &str = "HX-Location";
|
||||
/// Empuja la URL indicada al historial del navegador. Ver
|
||||
/// [`HtmxResponse::push_url()`](crate::response::HtmxResponse::push_url).
|
||||
/// [`HtmxResponse::push_url()`](crate::HtmxResponse::push_url).
|
||||
pub const PUSH_URL: &str = "HX-Push-Url";
|
||||
/// Provoca una redirección completa del navegador. Ver
|
||||
/// [`HtmxResponse::redirect()`](crate::response::HtmxResponse::redirect).
|
||||
/// [`HtmxResponse::redirect()`](crate::HtmxResponse::redirect).
|
||||
pub const REDIRECT: &str = "HX-Redirect";
|
||||
/// Provoca una recarga completa de la página. Ver
|
||||
/// [`HtmxResponse::refresh()`](crate::response::HtmxResponse::refresh).
|
||||
/// [`HtmxResponse::refresh()`](crate::HtmxResponse::refresh).
|
||||
pub const REFRESH: &str = "HX-Refresh";
|
||||
/// Reemplaza la URL actual en el historial. Ver
|
||||
/// [`HtmxResponse::replace_url()`](crate::response::HtmxResponse::replace_url).
|
||||
/// [`HtmxResponse::replace_url()`](crate::HtmxResponse::replace_url).
|
||||
pub const REPLACE_URL: &str = "HX-Replace-Url";
|
||||
/// Anula el `hx-swap` del elemento. Ver
|
||||
/// [`HtmxResponse::reswap()`](crate::response::HtmxResponse::reswap).
|
||||
/// [`HtmxResponse::reswap()`](crate::HtmxResponse::reswap).
|
||||
pub const RESWAP: &str = "HX-Reswap";
|
||||
/// Anula el `hx-target` del elemento. Ver
|
||||
/// [`HtmxResponse::retarget()`](crate::response::HtmxResponse::retarget).
|
||||
/// [`HtmxResponse::retarget()`](crate::HtmxResponse::retarget).
|
||||
pub const RETARGET: &str = "HX-Retarget";
|
||||
/// Anula el `hx-select` del elemento. Ver
|
||||
/// [`HtmxResponse::reselect()`](crate::response::HtmxResponse::reselect).
|
||||
/// [`HtmxResponse::reselect()`](crate::HtmxResponse::reselect).
|
||||
pub const RESELECT: &str = "HX-Reselect";
|
||||
/// Dispara eventos JavaScript al completar la respuesta. Ver
|
||||
/// [`HtmxResponse::trigger()`](crate::response::HtmxResponse::trigger).
|
||||
/// [`HtmxResponse::trigger()`](crate::HtmxResponse::trigger).
|
||||
pub const TRIGGER: &str = "HX-Trigger";
|
||||
/// Dispara eventos tras la fase *settle*. Ver
|
||||
/// [`HtmxResponse::trigger_after_settle()`](crate::response::HtmxResponse::trigger_after_settle).
|
||||
/// [`HtmxResponse::trigger_after_settle()`](crate::HtmxResponse::trigger_after_settle).
|
||||
pub const TRIGGER_AFTER_SETTLE: &str = "HX-Trigger-After-Settle";
|
||||
/// Dispara eventos tras el *swap*. Ver
|
||||
/// [`HtmxResponse::trigger_after_swap()`](crate::response::HtmxResponse::trigger_after_swap).
|
||||
/// [`HtmxResponse::trigger_after_swap()`](crate::HtmxResponse::trigger_after_swap).
|
||||
pub const TRIGGER_AFTER_SWAP: &str = "HX-Trigger-After-Swap";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -93,13 +93,18 @@ include_locales!(LOCALES_HTMX);
|
|||
|
||||
pub mod hx;
|
||||
pub mod hx_table;
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
|
||||
mod request;
|
||||
pub use request::HtmxRequestExt;
|
||||
|
||||
mod response;
|
||||
pub use response::HtmxResponse;
|
||||
|
||||
/// Prelude de `pagetop-htmx`.
|
||||
pub mod prelude {
|
||||
pub use crate::hx;
|
||||
pub use crate::hx_table;
|
||||
|
||||
pub use crate::request::HtmxRequestExt;
|
||||
pub use crate::response::HtmxResponse;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ use pagetop::prelude::*;
|
|||
|
||||
// **< HtmxRequestExt >*****************************************************************************
|
||||
|
||||
/// Extiende [`HttpRequest`] con métodos para detectar y leer peticiones HTMX.
|
||||
/// Extiende [`HttpRequest`](pagetop::web::HttpRequest) con métodos para detectar y leer peticiones
|
||||
/// HTMX.
|
||||
///
|
||||
/// HTMX añade cabeceras especiales a cada petición AJAX. Este trait permite acceder a ellas de
|
||||
/// forma expresiva, sin manipular [`pagetop::web::http::HeaderMap`] directamente.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
//! Implementación de [`HtmxResponse`] e [`IntoResponse`] para HTMX.
|
||||
//! Implementación de [`HtmxResponse`] e [`IntoResponse`](pagetop::web::IntoResponse) para HTMX.
|
||||
|
||||
use pagetop::prelude::*;
|
||||
|
||||
|
|
@ -10,7 +10,8 @@ use pagetop::prelude::*;
|
|||
/// parciales acompañados de cabeceras especiales que instruyen al cliente sobre qué hacer con la
|
||||
/// respuesta: actualizar la URL del historial, disparar eventos JavaScript, redirigir, etc.
|
||||
///
|
||||
/// Implementa [`IntoResponse`], por lo que puede devolverse directamente desde cualquier handler.
|
||||
/// Implementa [`IntoResponse`](pagetop::web::IntoResponse), por lo que puede devolverse
|
||||
/// directamente desde cualquier handler.
|
||||
///
|
||||
/// # Ejemplo
|
||||
///
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use pagetop::prelude::*;
|
||||
use pagetop_htmx::prelude::*;
|
||||
use pagetop_htmx::HtmxRequestExt;
|
||||
|
||||
struct TestApp;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ use crate::core::component::{Component, Context};
|
|||
use crate::html::{Markup, html};
|
||||
use crate::{AutoDefault, UniqueId, builder_fn};
|
||||
|
||||
use parking_lot::RwLock;
|
||||
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
use std::vec::IntoIter;
|
||||
|
|
@ -9,19 +11,14 @@ use std::vec::IntoIter;
|
|||
// **< Child >**************************************************************************************
|
||||
|
||||
/// Representa un componente hijo encapsulado para su uso en una lista [`Children`].
|
||||
///
|
||||
/// Envuelve el componente en `Arc<dyn Component>`, compartido y de sólo lectura. Clonar un `Child`
|
||||
/// sólo incrementa el contador de referencias. Para renderizar obtiene una copia propia con
|
||||
/// [`ComponentClone::clone_box()`](crate::core::component::ComponentClone::clone_box), de modo que
|
||||
/// el componente original nunca se modifica.
|
||||
#[derive(AutoDefault, Clone)]
|
||||
pub struct Child(Option<Arc<dyn Component>>);
|
||||
pub struct Child(Option<Arc<RwLock<Box<dyn Component>>>>);
|
||||
|
||||
impl fmt::Debug for Child {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match &self.0 {
|
||||
None => write!(f, "Child(None)"),
|
||||
Some(c) => write!(f, "Child({})", c.name()),
|
||||
Some(c) => write!(f, "Child({})", c.read().name()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -29,14 +26,7 @@ impl fmt::Debug for Child {
|
|||
impl Child {
|
||||
/// Crea un nuevo `Child` a partir de un componente.
|
||||
pub fn with(component: impl Component) -> Self {
|
||||
Child(Some(Arc::new(component)))
|
||||
}
|
||||
|
||||
// Envuelve un `Arc` ya construido, sin clonar el componente. La usa `core::theme::regions` para
|
||||
// registrar los prototipos de `InRegion` sin clonar su estado hasta que `render()` obtenga la
|
||||
// copia propia que necesita mutar.
|
||||
pub(crate) fn from_arc(component: Arc<dyn Component>) -> Self {
|
||||
Child(Some(component))
|
||||
Child(Some(Arc::new(RwLock::new(Box::new(component)))))
|
||||
}
|
||||
|
||||
// **< Child BUILDER >**************************************************************************
|
||||
|
|
@ -46,7 +36,7 @@ impl Child {
|
|||
/// Si se proporciona `Some(component)`, se encapsula como [`Child`]; y si es `None`, se limpia.
|
||||
#[builder_fn]
|
||||
pub fn with_component<C: Component>(mut self, component: Option<C>) -> Self {
|
||||
self.0 = component.map(|c| Arc::new(c) as Arc<dyn Component>);
|
||||
self.0 = component.map(|c| Arc::new(RwLock::new(Box::new(c) as Box<dyn Component>)));
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -55,7 +45,7 @@ impl Child {
|
|||
/// Devuelve el identificador del componente, si existe y está definido.
|
||||
#[inline]
|
||||
pub fn id(&self) -> Option<String> {
|
||||
self.0.as_ref().and_then(|c| c.id())
|
||||
self.0.as_ref().and_then(|c| c.read().id())
|
||||
}
|
||||
|
||||
// **< Child RENDER >***************************************************************************
|
||||
|
|
@ -65,7 +55,7 @@ impl Child {
|
|||
match &self.0 {
|
||||
None => html! {},
|
||||
Some(m) => {
|
||||
let mut component = m.clone_box();
|
||||
let mut component = m.read().clone_box();
|
||||
component.render(cx).await
|
||||
}
|
||||
}
|
||||
|
|
@ -76,39 +66,39 @@ impl Child {
|
|||
// Devuelve el [`UniqueId`] del tipo del componente, si el Child no está vacío.
|
||||
#[inline]
|
||||
fn type_id(&self) -> Option<UniqueId> {
|
||||
self.0.as_ref().map(|c| c.type_id())
|
||||
self.0.as_ref().map(|c| c.read().type_id())
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Component> From<Embed<C>> for Child {
|
||||
impl<C: Component + 'static> From<Embed<C>> for Child {
|
||||
/// Convierte un [`Embed<C>`] en un [`Child`], consumiendo el componente tipado.
|
||||
///
|
||||
/// Útil cuando se tiene un [`Embed`] para añadir a una lista [`Children`]:
|
||||
/// Útil cuando se tiene un [`Embed`] y se necesita añadirlo a una lista [`Children`]:
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use pagetop::prelude::*;
|
||||
/// let my_embed = Embed::with(Html::with(|_| html! { "Text" }));
|
||||
/// let children = Children::new().with_child(Child::from(my_embed));
|
||||
///
|
||||
/// // De forma equivalente se puede usar la conversión implícita hacia `Child`:
|
||||
/// let my_embed = Embed::with(Html::with(|_| html! { "Text" }));
|
||||
/// let child: Child = my_embed.into();
|
||||
/// let children = children.with_child(child);
|
||||
/// ```rust,ignore
|
||||
/// children.with_child(Child::from(my_embed));
|
||||
/// // o equivalentemente:
|
||||
/// children.with_child(my_embed.into());
|
||||
/// ```
|
||||
fn from(embed: Embed<C>) -> Self {
|
||||
Child(embed.0.map(|arc| arc as Arc<dyn Component>))
|
||||
match embed.0 {
|
||||
None => Child(None),
|
||||
Some(arc) => Child(Some(Arc::new(RwLock::new(match Arc::try_unwrap(arc) {
|
||||
Ok(c) => Box::new(c) as Box<dyn Component>,
|
||||
Err(arc) => arc.clone_box(),
|
||||
})))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component> From<T> for Child {
|
||||
/// Convierte cualquier componente en un [`Child`], equivalente a [`Child::with()`].
|
||||
impl<T: Component + 'static> From<T> for Child {
|
||||
#[inline]
|
||||
fn from(component: T) -> Self {
|
||||
Child::with(component)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component> From<T> for ChildOp {
|
||||
impl<T: Component + 'static> From<T> for ChildOp {
|
||||
/// Convierte un componente en [`ChildOp::Add`], permitiendo pasar componentes directamente a
|
||||
/// métodos como [`Children::with_child`] sin envolverlos explícitamente.
|
||||
#[inline]
|
||||
|
|
@ -280,13 +270,12 @@ pub enum ChildOp {
|
|||
/// Gracias a esto, [`with_child`](Self::with_child) acepta un componente directamente o cualquier
|
||||
/// variante de [`ChildOp`]:
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use pagetop::prelude::*;
|
||||
/// ```rust,ignore
|
||||
/// // Añadir al final de la lista (implícito):
|
||||
/// let children = Children::new().with_child(Html::new());
|
||||
/// children.with_child(MiComponente::new());
|
||||
///
|
||||
/// // Operación explícita:
|
||||
/// let children = children.with_child(ChildOp::Prepend(Html::new().into()));
|
||||
/// children.with_child(ChildOp::Prepend(MiComponente::new().into()));
|
||||
/// ```
|
||||
#[derive(AutoDefault, Clone, Debug)]
|
||||
pub struct Children(Vec<Child>);
|
||||
|
|
@ -381,12 +370,8 @@ impl Children {
|
|||
// **< Children HELPERS >***********************************************************************
|
||||
|
||||
// Añade más de un componente hijo al final de la lista (en el orden recibido).
|
||||
//
|
||||
// Usa `Vec::extend()`, que reserva la capacidad necesaria de una vez a partir del `size_hint()`
|
||||
// del iterador, en vez de una reasignación incremental por cada `push()`. También lo usa
|
||||
// `core::theme::regions` para fusionar las fuentes de una región.
|
||||
#[inline]
|
||||
pub(crate) fn add_many<I>(&mut self, iter: I) -> &mut Self
|
||||
fn add_many<I>(&mut self, iter: I) -> &mut Self
|
||||
where
|
||||
I: IntoIterator<Item = Child>,
|
||||
{
|
||||
|
|
@ -473,9 +458,8 @@ impl IntoIterator for Children {
|
|||
///
|
||||
/// # Ejemplo
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use pagetop::prelude::*;
|
||||
/// let children = Children::new().with_child(Html::new()).with_child(Html::new());
|
||||
/// ```rust,ignore
|
||||
/// let children = Children::new().with_child(child1).with_child(child2);
|
||||
/// for child in children {
|
||||
/// println!("{:?}", child.id());
|
||||
/// }
|
||||
|
|
@ -493,9 +477,8 @@ impl<'a> IntoIterator for &'a Children {
|
|||
///
|
||||
/// # Ejemplo
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use pagetop::prelude::*;
|
||||
/// let children = Children::new().with_child(Html::new()).with_child(Html::new());
|
||||
/// ```rust,ignore
|
||||
/// let children = Children::new().with_child(child1).with_child(child2);
|
||||
/// for child in &children {
|
||||
/// println!("{:?}", child.id());
|
||||
/// }
|
||||
|
|
@ -513,12 +496,10 @@ impl<'a> IntoIterator for &'a mut Children {
|
|||
///
|
||||
/// # Ejemplo
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use pagetop::prelude::*;
|
||||
/// async fn render_all(mut children: Children, context: &mut Context) {
|
||||
/// ```rust,ignore
|
||||
/// let mut children = Children::new().with_child(child1).with_child(child2);
|
||||
/// for child in &mut children {
|
||||
/// child.render(context).await;
|
||||
/// }
|
||||
/// child.render(&mut context).await;
|
||||
/// }
|
||||
/// ```
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
|
|
|
|||
|
|
@ -7,53 +7,40 @@ use parking_lot::RwLock;
|
|||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, LazyLock};
|
||||
|
||||
// Lista de prototipos de componentes por nombre de región.
|
||||
// Permite almacenar un componente como prototipo en regiones globales.
|
||||
//
|
||||
// Utiliza Vec en lugar de HashMap. El número de regiones registradas por tema o aplicación es casi
|
||||
// siempre de un dígito, así que una búsqueda lineal por igualdad de `&str` evita el coste de
|
||||
// hashear la clave. Además, el trabajo para recorrer regiones vacías es mínimo.
|
||||
//
|
||||
// La clave es `&'static str` (lo que ya devuelve `RegionName::name()`) en lugar de `String`. No
|
||||
// hace falta reservar en el heap una copia de un dato que ya vive de forma estática.
|
||||
#[derive(AutoDefault)]
|
||||
struct RegionComponents(Vec<(&'static str, Vec<Arc<dyn Component>>)>);
|
||||
|
||||
impl RegionComponents {
|
||||
// Devuelve los prototipos registrados para la región indicada, si hay alguno.
|
||||
fn get(&self, region_name: &str) -> Option<&Vec<Arc<dyn Component>>> {
|
||||
self.0
|
||||
.iter()
|
||||
.find(|(name, _)| *name == region_name)
|
||||
.map(|(_, protos)| protos)
|
||||
// Se implementa automáticamente para todo tipo que implemente [`Component`] y [`Clone`]. En cada
|
||||
// llamada a [`as_child`](Self::as_child) produce un clon fresco del estado original, de modo que
|
||||
// cada página renderiza el componente desde su estado inicial sin acumular mutaciones de peticiones
|
||||
// anteriores.
|
||||
trait ComponentGlobal: Send + Sync {
|
||||
// Devuelve un nuevo [`Child`] con una copia independiente del componente original.
|
||||
fn as_child(&self) -> Child;
|
||||
}
|
||||
|
||||
// Añade un prototipo a la región indicada, creando la entrada si es la primera.
|
||||
//
|
||||
// Se comparte como `Arc<dyn Component>`. El prototipo no se clona aquí ni al ensamblar la
|
||||
// región (sólo se clona el `Arc`, barato). El único clonado real del componente ocurre en
|
||||
// `Child::render()`, cuando cada petición necesita su propia copia mutable para pasar por
|
||||
// `setup()` desde un estado inicial limpio.
|
||||
fn push(&mut self, region_name: &'static str, proto: Arc<dyn Component>) {
|
||||
match self.0.iter_mut().find(|(name, _)| *name == region_name) {
|
||||
Some((_, protos)) => protos.push(proto),
|
||||
None => self.0.push((region_name, vec![proto])),
|
||||
}
|
||||
impl<T: Component + Clone + 'static> ComponentGlobal for T {
|
||||
#[inline]
|
||||
fn as_child(&self) -> Child {
|
||||
Child::with(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
// Mapa de nombre de región a lista de prototipos de componentes.
|
||||
type RegionComponents = HashMap<String, Vec<Arc<dyn ComponentGlobal>>>;
|
||||
|
||||
// Regiones globales con prototipos asociados a un tema específico.
|
||||
static THEME_REGIONS: LazyLock<RwLock<HashMap<UniqueId, RegionComponents>>> =
|
||||
LazyLock::new(|| RwLock::new(HashMap::new()));
|
||||
|
||||
// Regiones globales con prototipos comunes a todos los temas.
|
||||
static COMMON_REGIONS: LazyLock<RwLock<RegionComponents>> =
|
||||
LazyLock::new(|| RwLock::new(RegionComponents::default()));
|
||||
LazyLock::new(|| RwLock::new(HashMap::new()));
|
||||
|
||||
// *************************************************************************************************
|
||||
|
||||
// Contenedor interno de componentes agrupados por región.
|
||||
#[derive(AutoDefault)]
|
||||
pub(crate) struct ChildrenInRegions(HashMap<&'static str, Children>);
|
||||
pub(crate) struct ChildrenInRegions(HashMap<String, Children>);
|
||||
|
||||
impl ChildrenInRegions {
|
||||
pub fn with(region: RegionRef, child: Child) -> Self {
|
||||
|
|
@ -68,7 +55,7 @@ impl ChildrenInRegions {
|
|||
region.alter_child(child);
|
||||
} else {
|
||||
let children = Children::new().with_child(child);
|
||||
self.0.insert(region_name, children);
|
||||
self.0.insert(region_name.to_owned(), children);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
|
@ -77,39 +64,38 @@ impl ChildrenInRegions {
|
|||
///
|
||||
/// Se recogen desde tres fuentes disponibles, en el siguiente orden:
|
||||
///
|
||||
/// 1. Prototipos globales comunes, disponibles en cualquier tema. Se comparten como `Arc` (sin
|
||||
/// clonar el componente); `Child::render()` obtiene su propia copia mutable más adelante,
|
||||
/// 1. Prototipos globales comunes, disponibles en cualquier tema. Se clonan en cada petición
|
||||
/// para que `setup()` parta siempre de un estado inicial limpio.
|
||||
/// 2. Componentes propios de la página, registrados para esta petición concreta. Se mueven en
|
||||
/// lugar de clonarse, ya que son de un único uso.
|
||||
/// 3. Prototipos del tema activo, exclusivos del tema en curso. Se comparten igual que los
|
||||
/// comunes.
|
||||
/// 3. Prototipos del tema activo, exclusivos del tema en curso. También se clonan para asegurar
|
||||
/// que llegan a `setup()` con el mismo estado inicial.
|
||||
pub fn assemble_region(&mut self, theme: ThemeRef, region: RegionRef) -> Children {
|
||||
let region_name = region.name();
|
||||
let common = COMMON_REGIONS.read();
|
||||
let themed = THEME_REGIONS.read();
|
||||
|
||||
let mut result = Children::new();
|
||||
|
||||
let region_name = region.name();
|
||||
|
||||
// 1. Prototipos globales comunes.
|
||||
if let Some(global_protos) = COMMON_REGIONS.read().get(region_name) {
|
||||
result.add_many(
|
||||
global_protos
|
||||
.iter()
|
||||
.map(|proto| Child::from_arc(Arc::clone(proto))),
|
||||
);
|
||||
if let Some(protos) = common.get(region_name) {
|
||||
for proto in protos {
|
||||
result.add(proto.as_child());
|
||||
}
|
||||
}
|
||||
// 2. Componentes propios de la página: se mueven, no se clonan.
|
||||
if let Some(page_children) = self.0.remove(region_name) {
|
||||
result.add_many(page_children);
|
||||
for child in page_children {
|
||||
result.add(child);
|
||||
}
|
||||
}
|
||||
// 3. Prototipos del tema activo.
|
||||
if let Some(theme_region) = THEME_REGIONS.read().get(&theme.type_id())
|
||||
&& let Some(theme_protos) = theme_region.get(region_name)
|
||||
{
|
||||
result.add_many(
|
||||
theme_protos
|
||||
.iter()
|
||||
.map(|proto| Child::from_arc(Arc::clone(proto))),
|
||||
);
|
||||
if let Some(theme_map) = themed.get(&theme.type_id()) {
|
||||
if let Some(protos) = theme_map.get(region_name) {
|
||||
for proto in protos {
|
||||
result.add(proto.as_child());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
|
|
@ -182,8 +168,8 @@ impl InRegion {
|
|||
/// html! { "Aviso legal" }
|
||||
/// }));
|
||||
/// ```
|
||||
pub fn add(&self, component: impl Component) -> &Self {
|
||||
let proto: Arc<dyn Component> = Arc::new(component);
|
||||
pub fn add(&self, component: impl Component + Clone + 'static) -> &Self {
|
||||
let proto: Arc<dyn ComponentGlobal> = Arc::new(component);
|
||||
match self {
|
||||
InRegion::Content => Self::add_to_common(&CoreRegion::Content, proto),
|
||||
InRegion::Global(region) => Self::add_to_common(*region, proto),
|
||||
|
|
@ -192,14 +178,20 @@ impl InRegion {
|
|||
.write()
|
||||
.entry(theme.type_id())
|
||||
.or_default()
|
||||
.push((*region).name(), proto);
|
||||
.entry((*region).name().to_owned())
|
||||
.or_default()
|
||||
.push(proto);
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn add_to_common(region: RegionRef, proto: Arc<dyn Component>) {
|
||||
COMMON_REGIONS.write().push(region.name(), proto);
|
||||
fn add_to_common(region: RegionRef, proto: Arc<dyn ComponentGlobal>) {
|
||||
COMMON_REGIONS
|
||||
.write()
|
||||
.entry(region.name().to_owned())
|
||||
.or_default()
|
||||
.push(proto);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
20
src/web.rs
20
src/web.rs
|
|
@ -347,7 +347,6 @@ pub mod test {
|
|||
pub struct TestRequest {
|
||||
method: http::Method,
|
||||
uri: String,
|
||||
headers: http::HeaderMap,
|
||||
extensions: http::Extensions,
|
||||
}
|
||||
|
||||
|
|
@ -357,7 +356,6 @@ pub mod test {
|
|||
Self {
|
||||
method: http::Method::GET,
|
||||
uri: "/".to_owned(),
|
||||
headers: http::HeaderMap::new(),
|
||||
extensions: http::Extensions::new(),
|
||||
}
|
||||
}
|
||||
|
|
@ -367,7 +365,6 @@ pub mod test {
|
|||
Self {
|
||||
method: http::Method::POST,
|
||||
uri: "/".to_owned(),
|
||||
headers: http::HeaderMap::new(),
|
||||
extensions: http::Extensions::new(),
|
||||
}
|
||||
}
|
||||
|
|
@ -378,20 +375,6 @@ pub mod test {
|
|||
self
|
||||
}
|
||||
|
||||
/// Añade una cabecera a la petición.
|
||||
///
|
||||
/// Si `name` o `value` no son válidos como cabecera HTTP, se descarta en silencio en lugar
|
||||
/// de entrar en pánico.
|
||||
pub fn header(mut self, name: impl AsRef<str>, value: impl AsRef<str>) -> Self {
|
||||
if let (Ok(n), Ok(v)) = (
|
||||
http::HeaderName::from_bytes(name.as_ref().as_bytes()),
|
||||
http::HeaderValue::from_str(value.as_ref()),
|
||||
) {
|
||||
self.headers.insert(n, v);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Inserta un valor en las extensiones de la petición.
|
||||
///
|
||||
/// Útil para simular lo que un middleware haría en producción antes de que el handler
|
||||
|
|
@ -408,7 +391,6 @@ pub mod test {
|
|||
.uri(self.uri)
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
*req.headers_mut() = self.headers;
|
||||
*req.extensions_mut() = self.extensions;
|
||||
req
|
||||
}
|
||||
|
|
@ -419,7 +401,7 @@ pub mod test {
|
|||
let uri = self.uri.parse().unwrap();
|
||||
super::HttpRequest {
|
||||
uri,
|
||||
headers: self.headers,
|
||||
headers: axum::http::HeaderMap::new(),
|
||||
extensions: std::sync::Arc::new(self.extensions),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue