`Theme::handle_component()` se divide en `setup_component()` (síncrono, muta por tipo con la macro homónima) y `render_component()` (async, decide el renderizado con su propia macro). Aplica mismo criterio que ya separa `Component::setup()` de `prepare()`.
143 lines
5.2 KiB
Rust
143 lines
5.2 KiB
Rust
use crate::core::action::add_action;
|
|
use crate::core::extension::ExtensionRef;
|
|
use crate::core::theme::ThemeRef;
|
|
use crate::core::theme::all::THEMES;
|
|
use crate::web::Router;
|
|
use crate::{global, serve_static_files, trace, web};
|
|
|
|
use std::sync::OnceLock;
|
|
|
|
static EXTENSIONS: OnceLock<Vec<ExtensionRef>> = OnceLock::new();
|
|
|
|
// **< REGISTRO DE LAS EXTENSIONES >****************************************************************
|
|
|
|
pub fn register_extensions(root_extension: Option<ExtensionRef>) {
|
|
// Garantiza que ocurre sólo una vez cuando los tests se ejecutan en paralelo.
|
|
EXTENSIONS.get_or_init(|| {
|
|
let mut list: Vec<ExtensionRef> = Vec::new();
|
|
|
|
// Primero añade el tema básico a la lista de extensiones habilitadas.
|
|
add_to_enabled(&mut list, &crate::base::theme::Basic);
|
|
|
|
// Si se proporciona la extensión raíz inicial, se añade a las extensiones habilitadas.
|
|
if let Some(extension) = root_extension {
|
|
add_to_enabled(&mut list, extension);
|
|
}
|
|
|
|
// Añade la página de bienvenida si no hay extensión raíz.
|
|
if root_extension.is_none() {
|
|
add_to_enabled(&mut list, &crate::base::extension::Welcome);
|
|
}
|
|
|
|
list
|
|
});
|
|
}
|
|
|
|
fn add_to_enabled(list: &mut Vec<ExtensionRef>, extension: ExtensionRef) {
|
|
// Verifica que la extensión no esté en la lista para evitar duplicados.
|
|
if !list.iter().any(|e| e.type_id() == extension.type_id()) {
|
|
// Añade primero (en orden inverso) las dependencias de la extensión.
|
|
for d in extension.dependencies().into_iter().rev() {
|
|
add_to_enabled(list, d);
|
|
}
|
|
|
|
// Añade la propia extensión a la lista.
|
|
list.push(extension);
|
|
|
|
// Comprueba si la extensión tiene un tema asociado que deba registrarse.
|
|
if let Some(theme) = extension.theme() {
|
|
check_theme_parent_chain(theme);
|
|
|
|
let mut registered_themes = THEMES.write();
|
|
// Asegura que el tema no esté ya registrado para evitar duplicados.
|
|
if !registered_themes
|
|
.iter()
|
|
.any(|t| t.type_id() == theme.type_id())
|
|
{
|
|
registered_themes.push(theme);
|
|
trace::debug!("Enabling \"{}\" theme", theme.short_name());
|
|
}
|
|
} else {
|
|
trace::debug!("Enabling \"{}\" extension", extension.short_name());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Recorre la cadena de `Theme::parent()` para detectar referencias circulares. `parent()` se
|
|
// resuelve en tiempo de ejecución, así que un ciclo no puede descartarse al compilar. Se rechaza el
|
|
// arranque si detecta uno, antes de provocar un bucle infinito en el `ComponentRender::render()` o
|
|
// un desbordamiento de pila en los métodos predefinidos de `Theme` que delegan recursivamente en el
|
|
// tema padre.
|
|
fn check_theme_parent_chain(theme: ThemeRef) {
|
|
let mut chain: Vec<ThemeRef> = vec![theme];
|
|
let mut current = theme;
|
|
while let Some(parent) = current.parent() {
|
|
if let Some(pos) = chain.iter().position(|t| t.type_id() == parent.type_id()) {
|
|
let cycle: Vec<&str> = chain[pos..]
|
|
.iter()
|
|
.map(|t| t.short_name())
|
|
.chain(std::iter::once(parent.short_name()))
|
|
.collect();
|
|
panic!(
|
|
"Circular theme parent chain detected: {}",
|
|
cycle.join(" -> ")
|
|
);
|
|
}
|
|
chain.push(parent);
|
|
current = parent;
|
|
}
|
|
}
|
|
|
|
// **< REGISTRO DE LAS ACCIONES >*******************************************************************
|
|
|
|
pub fn register_actions() {
|
|
for extension in EXTENSIONS.get().into_iter().flatten() {
|
|
for a in extension.actions() {
|
|
add_action(a);
|
|
}
|
|
}
|
|
}
|
|
|
|
// **< INICIALIZA LAS EXTENSIONES >*****************************************************************
|
|
|
|
pub async fn initialize_extensions() {
|
|
trace::info!("Calling application bootstrap");
|
|
for e in EXTENSIONS.get().into_iter().flatten() {
|
|
e.initialize().await;
|
|
}
|
|
}
|
|
|
|
// **< CONFIGURA LAS RUTAS >************************************************************************
|
|
|
|
pub fn configure_routes(router: Router) -> Router {
|
|
// Sólo compila durante el desarrollo, para evitar errores 400 en la traza de eventos.
|
|
#[cfg(debug_assertions)]
|
|
let router = router.route(
|
|
"/.well-known/appspecific/com.chrome.devtools.json",
|
|
web::get(|| async { web::http::StatusCode::NOT_FOUND }),
|
|
);
|
|
|
|
let router = EXTENSIONS
|
|
.get()
|
|
.into_iter()
|
|
.flatten()
|
|
.fold(router, |r, e| e.configure_router(r));
|
|
|
|
serve_static_files!(router, [&global::SETTINGS.dev.pagetop_static_dir, assets] => "/pagetop");
|
|
|
|
router
|
|
}
|
|
|
|
// **< CONFIGURA EL MIDDLEWARE GLOBAL >*************************************************************
|
|
|
|
/// Aplica las capas de middleware globales de todas las extensiones sobre el router ya enrutado.
|
|
///
|
|
/// Se llama después de [`configure_routes`] para garantizar que las capas envuelven todas las
|
|
/// rutas de la aplicación, independientemente del orden de las extensiones.
|
|
pub fn configure_middleware(router: Router) -> Router {
|
|
EXTENSIONS
|
|
.get()
|
|
.into_iter()
|
|
.flatten()
|
|
.fold(router, |r, e| e.configure_middleware(r))
|
|
}
|