From 5b1064fda2a4ffd6f7bbdbc87b98d2a0b2a4762c Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Fri, 25 Mar 2022 20:24:19 +0100 Subject: [PATCH] =?UTF-8?q?Libera=20la=20versi=C3=B3n=20de=20desarrollo=20?= =?UTF-8?q?0.0.4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CREDITS.md | 11 ++++++-- config/default.toml | 2 +- pagetop-admin/src/lib.rs | 4 +++ pagetop-node/src/lib.rs | 4 +++ pagetop-user/src/lib.rs | 4 +++ pagetop/Cargo.toml | 2 +- pagetop/src/app/application.rs | 9 +++++-- pagetop/src/base/component/block.rs | 2 +- pagetop/src/base/component/form/button.rs | 2 +- pagetop/src/base/component/form/date.rs | 8 +++--- pagetop/src/base/component/form/form.rs | 4 +-- pagetop/src/base/component/form/hidden.rs | 2 +- pagetop/src/base/component/form/input.rs | 8 +++--- .../locales/en-US/demopage.ftl} | 2 +- .../locales/es-ES/demopage.ftl} | 2 +- .../base/module/{homepage => demopage}/mod.rs | 14 +++++++--- pagetop/src/base/module/mod.rs | 2 +- pagetop/src/base/theme/aliner/mod.rs | 6 ++++- pagetop/src/base/theme/bootsier/mod.rs | 4 +-- pagetop/src/base/theme/minimal/mod.rs | 2 +- pagetop/src/module/all.rs | 25 +++++++++++++----- pagetop/src/module/definition.rs | 10 +------ pagetop/src/response/page/page.rs | 6 ++--- pagetop/src/theme/all.rs | 17 ++++-------- pagetop/src/theme/definition.rs | 7 ++++- pagetop/src/util.rs | 12 ++++----- pagetop/static/theme/favicon.png | Bin 0 -> 11471 bytes 27 files changed, 104 insertions(+), 67 deletions(-) rename pagetop/src/base/module/{homepage/locales/en-US/homepage.ftl => demopage/locales/en-US/demopage.ftl} (95%) rename pagetop/src/base/module/{homepage/locales/es-ES/homepage.ftl => demopage/locales/es-ES/demopage.ftl} (93%) rename pagetop/src/base/module/{homepage => demopage}/mod.rs (91%) create mode 100644 pagetop/static/theme/favicon.png diff --git a/CREDITS.md b/CREDITS.md index 5b9979f4..4254ed59 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -1,9 +1,9 @@ # FIGfonts PageTop utiliza el paquete [figlet-rs](https://crates.io/crates/figlet-rs) de -*yuanbohan*, que muestra al inicio de la ejecución un rótulo con el nombre de +*yuanbohan*. Muestra en el terminal un rótulo de presentación con el nombre de la aplicación usando caracteres [FIGlet](http://www.figlet.org/). Las fuentes -incluidas en `resources` son: +incluidas en `src/app/banner` son: * [slant.flf](http://www.figlet.org/fontdb_example.cgi?font=slant.flf) por *Glenn Chappell*. @@ -13,3 +13,10 @@ incluidas en `resources` son: por *Claude Martins*. * [starwars.flf](http://www.figlet.org/fontdb_example.cgi?font=starwars.flf) por *Ryan Youck*. + +# Icono + +El monstruo sonriente de Frankenstein es una divertida creación de +[Webalys](https://www.iconfinder.com/webalys). Puede encontrarse en su colección +[Nasty Icons](https://www.iconfinder.com/iconsets/nasty) disponible en +[ICONFINDER](https://www.iconfinder.com/). \ No newline at end of file diff --git a/config/default.toml b/config/default.toml index 5b6982fe..13012a93 100644 --- a/config/default.toml +++ b/config/default.toml @@ -15,7 +15,7 @@ db_user = "drust" db_pass = "DrU__#3T" [log] -tracing = "Info,sqlx::query=Warn" +tracing = "Info,pagetop=Debug,sqlx::query=Warn" [dev] #static_files = "pagetop/static" diff --git a/pagetop-admin/src/lib.rs b/pagetop-admin/src/lib.rs index e3ad0337..a00e7fae 100644 --- a/pagetop-admin/src/lib.rs +++ b/pagetop-admin/src/lib.rs @@ -7,6 +7,10 @@ mod summary; pub struct AdminModule; impl ModuleTrait for AdminModule { + fn name(&self) -> &'static str { + "Admin" + } + fn fullname(&self) -> String { l("module_fullname") } diff --git a/pagetop-node/src/lib.rs b/pagetop-node/src/lib.rs index af6d8b3f..afcd729f 100644 --- a/pagetop-node/src/lib.rs +++ b/pagetop-node/src/lib.rs @@ -8,6 +8,10 @@ mod migration; pub struct NodeModule; impl ModuleTrait for NodeModule { + fn name(&self) -> &'static str { + "Node" + } + fn fullname(&self) -> String { l("module_fullname") } diff --git a/pagetop-user/src/lib.rs b/pagetop-user/src/lib.rs index c76233e1..343daa7a 100644 --- a/pagetop-user/src/lib.rs +++ b/pagetop-user/src/lib.rs @@ -8,6 +8,10 @@ mod migration; pub struct UserModule; impl ModuleTrait for UserModule { + fn name(&self) -> &'static str { + "User" + } + fn fullname(&self) -> String { l("module_fullname") } diff --git a/pagetop/Cargo.toml b/pagetop/Cargo.toml index 4d92b378..a260d17f 100644 --- a/pagetop/Cargo.toml +++ b/pagetop/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop" -version = "0.0.3" +version = "0.0.4" edition = "2021" authors = [ diff --git a/pagetop/src/app/application.rs b/pagetop/src/app/application.rs index 4ed2c1da..070390c8 100644 --- a/pagetop/src/app/application.rs +++ b/pagetop/src/app/application.rs @@ -27,13 +27,18 @@ impl Application { #[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))] Lazy::force(&app::db::DBCONN); + // Registra los temas predeterminados. + theme::register_theme(&base::theme::aliner::AlinerTheme); + theme::register_theme(&base::theme::minimal::MinimalTheme); + theme::register_theme(&base::theme::bootsier::BootsierTheme); + // Ejecuta la función de inicio de la aplicación. trace::info!("Calling application bootstrap"); let _ = &bootstrap(); - // Registra el módulo para una página de presentación de PageTop. + // Registra el módulo de presentación de PageTop. // Normalmente se sobrecargará en la función de inicio. - module::register_module(&base::module::homepage::HomepageModule); + module::register_module(&base::module::demopage::DemopageModule); // Actualizaciones pendientes de la base de datos (opcional). #[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))] diff --git a/pagetop/src/base/component/block.rs b/pagetop/src/base/component/block.rs index 4f658c9e..96fce677 100644 --- a/pagetop/src/base/component/block.rs +++ b/pagetop/src/base/component/block.rs @@ -71,7 +71,7 @@ impl Block { } pub fn with_title(mut self, title: &str) -> Self { - self.title = util::optional_str(title); + self.title = util::valid_str(title); self } diff --git a/pagetop/src/base/component/form/button.rs b/pagetop/src/base/component/form/button.rs index c76c49dd..57810fea 100644 --- a/pagetop/src/base/component/form/button.rs +++ b/pagetop/src/base/component/form/button.rs @@ -101,7 +101,7 @@ impl Button { } pub fn with_value(mut self, value: &str) -> Self { - self.value = util::optional_str(value); + self.value = util::valid_str(value); self } diff --git a/pagetop/src/base/component/form/date.rs b/pagetop/src/base/component/form/date.rs index ccc6ab31..6614670a 100644 --- a/pagetop/src/base/component/form/date.rs +++ b/pagetop/src/base/component/form/date.rs @@ -112,17 +112,17 @@ impl Date { } pub fn with_value(mut self, value: &str) -> Self { - self.value = util::optional_str(value); + self.value = util::valid_str(value); self } pub fn with_label(mut self, label: &str) -> Self { - self.label = util::optional_str(label); + self.label = util::valid_str(label); self } pub fn with_placeholder(mut self, placeholder: &str) -> Self { - self.placeholder = util::optional_str(placeholder); + self.placeholder = util::valid_str(placeholder); self } @@ -167,7 +167,7 @@ impl Date { } pub fn with_help_text(mut self, help_text: &str) -> Self { - self.help_text = util::optional_str(help_text); + self.help_text = util::valid_str(help_text); self } diff --git a/pagetop/src/base/component/form/form.rs b/pagetop/src/base/component/form/form.rs index 63eace0c..2fac4403 100644 --- a/pagetop/src/base/component/form/form.rs +++ b/pagetop/src/base/component/form/form.rs @@ -76,7 +76,7 @@ impl Form { } pub fn with_action(mut self, action: &str) -> Self { - self.action = util::optional_str(action); + self.action = util::valid_str(action); self } @@ -86,7 +86,7 @@ impl Form { } pub fn with_charset(mut self, charset: &str) -> Self { - self.charset = util::optional_str(charset); + self.charset = util::valid_str(charset); self } diff --git a/pagetop/src/base/component/form/hidden.rs b/pagetop/src/base/component/form/hidden.rs index 9ac0a4f9..bd158b85 100644 --- a/pagetop/src/base/component/form/hidden.rs +++ b/pagetop/src/base/component/form/hidden.rs @@ -54,7 +54,7 @@ impl Hidden { } pub fn with_value(mut self, value: &str) -> Self { - self.value = util::optional_str(value); + self.value = util::valid_str(value); self } diff --git a/pagetop/src/base/component/form/input.rs b/pagetop/src/base/component/form/input.rs index 884d2232..f0e73a63 100644 --- a/pagetop/src/base/component/form/input.rs +++ b/pagetop/src/base/component/form/input.rs @@ -167,12 +167,12 @@ impl Input { } pub fn with_value(mut self, value: &str) -> Self { - self.value = util::optional_str(value); + self.value = util::valid_str(value); self } pub fn with_label(mut self, label: &str) -> Self { - self.label = util::optional_str(label); + self.label = util::valid_str(label); self } @@ -192,7 +192,7 @@ impl Input { } pub fn with_placeholder(mut self, placeholder: &str) -> Self { - self.placeholder = util::optional_str(placeholder); + self.placeholder = util::valid_str(placeholder); self } @@ -237,7 +237,7 @@ impl Input { } pub fn with_help_text(mut self, help_text: &str) -> Self { - self.help_text = util::optional_str(help_text); + self.help_text = util::valid_str(help_text); self } diff --git a/pagetop/src/base/module/homepage/locales/en-US/homepage.ftl b/pagetop/src/base/module/demopage/locales/en-US/demopage.ftl similarity index 95% rename from pagetop/src/base/module/homepage/locales/en-US/homepage.ftl rename to pagetop/src/base/module/demopage/locales/en-US/demopage.ftl index 39a5e426..14c93f2f 100644 --- a/pagetop/src/base/module/homepage/locales/en-US/homepage.ftl +++ b/pagetop/src/base/module/demopage/locales/en-US/demopage.ftl @@ -1,5 +1,5 @@ module_fullname = Default homepage -module_description = Displays a default homepage when none is configured. +module_description = Displays a demo homepage when none is configured. page_title = Hello world! diff --git a/pagetop/src/base/module/homepage/locales/es-ES/homepage.ftl b/pagetop/src/base/module/demopage/locales/es-ES/demopage.ftl similarity index 93% rename from pagetop/src/base/module/homepage/locales/es-ES/homepage.ftl rename to pagetop/src/base/module/demopage/locales/es-ES/demopage.ftl index f1d85b2e..39720fcd 100644 --- a/pagetop/src/base/module/homepage/locales/es-ES/homepage.ftl +++ b/pagetop/src/base/module/demopage/locales/es-ES/demopage.ftl @@ -1,5 +1,5 @@ module_fullname = Página de inicio predeterminada -module_description = Muestra una página de inicio predeterminada cuando no hay ninguna configurada. +module_description = Muestra una página de demostración predeterminada cuando no hay ninguna configurada. page_title = ¡Hola mundo! diff --git a/pagetop/src/base/module/homepage/mod.rs b/pagetop/src/base/module/demopage/mod.rs similarity index 91% rename from pagetop/src/base/module/homepage/mod.rs rename to pagetop/src/base/module/demopage/mod.rs index bd364534..bb6e3b94 100644 --- a/pagetop/src/base/module/homepage/mod.rs +++ b/pagetop/src/base/module/demopage/mod.rs @@ -1,10 +1,14 @@ use crate::prelude::*; -localize!("src/base/module/homepage/locales"); +localize!("src/base/module/demopage/locales"); -pub struct HomepageModule; +pub struct DemopageModule; + +impl ModuleTrait for DemopageModule { + fn name(&self) -> &'static str { + "Demopage" + } -impl ModuleTrait for HomepageModule { fn fullname(&self) -> String { l("module_fullname") } @@ -20,9 +24,13 @@ impl ModuleTrait for HomepageModule { async fn home() -> app::Result { Page::prepare() + .using_theme("Bootsier") .with_title( l("page_title").as_str() ) + + + .add_to("content", Container::prepare() .with_id("welcome") .add(Chunck::markup(html! { diff --git a/pagetop/src/base/module/mod.rs b/pagetop/src/base/module/mod.rs index 070e5b82..7107d180 100644 --- a/pagetop/src/base/module/mod.rs +++ b/pagetop/src/base/module/mod.rs @@ -1 +1 @@ -pub mod homepage; +pub mod demopage; diff --git a/pagetop/src/base/theme/aliner/mod.rs b/pagetop/src/base/theme/aliner/mod.rs index 07b1470f..d4f4f252 100644 --- a/pagetop/src/base/theme/aliner/mod.rs +++ b/pagetop/src/base/theme/aliner/mod.rs @@ -6,7 +6,7 @@ pub struct AlinerTheme; impl ThemeTrait for AlinerTheme { fn name(&self) -> &'static str { - "aliner" + "Aliner" } fn fullname(&self) -> String { @@ -19,6 +19,10 @@ impl ThemeTrait for AlinerTheme { fn before_render_page(&self, page: &mut Page) { page.assets() + .with_favicon( + Favicon::new() + .with_icon("/theme/favicon.png") + ) .add_stylesheet( StyleSheet::source( "/aliner/css/styles.css" diff --git a/pagetop/src/base/theme/bootsier/mod.rs b/pagetop/src/base/theme/bootsier/mod.rs index dbd31727..ada65b0e 100644 --- a/pagetop/src/base/theme/bootsier/mod.rs +++ b/pagetop/src/base/theme/bootsier/mod.rs @@ -8,7 +8,7 @@ pub struct BootsierTheme; impl ThemeTrait for BootsierTheme { fn name(&self) -> &'static str { - "bootsier" + "Bootsier" } fn fullname(&self) -> String { @@ -23,7 +23,7 @@ impl ThemeTrait for BootsierTheme { page.assets() .with_favicon( Favicon::new() - .with_icon("/bootsier/favicon.png") + .with_icon("/theme/favicon.png") ) .add_stylesheet( StyleSheet::source( diff --git a/pagetop/src/base/theme/minimal/mod.rs b/pagetop/src/base/theme/minimal/mod.rs index b3a50906..471b3738 100644 --- a/pagetop/src/base/theme/minimal/mod.rs +++ b/pagetop/src/base/theme/minimal/mod.rs @@ -4,7 +4,7 @@ pub struct MinimalTheme; impl ThemeTrait for MinimalTheme { fn name(&self) -> &'static str { - "minimal" + "Minimal" } fn fullname(&self) -> String { diff --git a/pagetop/src/module/all.rs b/pagetop/src/module/all.rs index 120c59b6..c95583e9 100644 --- a/pagetop/src/module/all.rs +++ b/pagetop/src/module/all.rs @@ -10,13 +10,24 @@ static MODULES: Lazy>> = Lazy::new(|| { }); pub fn register_module(module: &'static dyn ModuleTrait) { - let mut modules = MODULES.write().unwrap(); - match modules.iter().find(|m| m.name() == module.name()) { - None => { - trace::info!("{}", module.name()); - modules.push(module); - }, - Some(_) => {}, + let mut list: Vec<&dyn ModuleTrait> = Vec::new(); + add_to(&mut list, module); + list.reverse(); + MODULES.write().unwrap().append(&mut list); +} + +fn add_to(list: &mut Vec<&dyn ModuleTrait>, module: &'static dyn ModuleTrait) { + if !MODULES.read().unwrap().iter().any(|m| m.name() == module.name()) { + if !list.iter().any(|m| m.name() == module.name()) { + trace::debug!("Registering \"{}\" module", module.name()); + list.push(module); + + let mut dependencies = module.dependencies(); + dependencies.reverse(); + for d in dependencies.iter() { + add_to(list, *d); + } + } } } diff --git a/pagetop/src/module/definition.rs b/pagetop/src/module/definition.rs index 9424fbd8..b01f9801 100644 --- a/pagetop/src/module/definition.rs +++ b/pagetop/src/module/definition.rs @@ -3,17 +3,9 @@ use crate::app; #[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))] use crate::db; -use std::any::type_name; - /// Los módulos deben implementar este *trait*. pub trait ModuleTrait: Send + Sync { - fn name(&self) -> &'static str { - let name = type_name::(); - match name.rfind("::") { - Some(position) => &name[(position + 2)..], - None => name - } - } + fn name(&self) -> &'static str; fn fullname(&self) -> String; diff --git a/pagetop/src/response/page/page.rs b/pagetop/src/response/page/page.rs index 58541d22..eff38daa 100644 --- a/pagetop/src/response/page/page.rs +++ b/pagetop/src/response/page/page.rs @@ -75,7 +75,7 @@ impl<'a> Page<'a> { // Page BUILDER. pub fn with_language(&mut self, language: &str) -> &mut Self { - self.language = util::optional_str(language); + self.language = util::valid_str(language); self } @@ -89,12 +89,12 @@ impl<'a> Page<'a> { } pub fn with_title(&mut self, title: &str) -> &mut Self { - self.title = util::optional_str(title); + self.title = util::valid_str(title); self } pub fn with_description(&mut self, description: &str) -> &mut Self { - self.description = util::optional_str(description); + self.description = util::valid_str(description); self } diff --git a/pagetop/src/theme/all.rs b/pagetop/src/theme/all.rs index cc25d9d8..ee0bfedb 100644 --- a/pagetop/src/theme/all.rs +++ b/pagetop/src/theme/all.rs @@ -1,4 +1,4 @@ -use crate::{Lazy, app, base, theme_static_files, trace}; +use crate::{Lazy, app, theme_static_files, trace}; use super::ThemeTrait; use std::sync::RwLock; @@ -7,21 +7,14 @@ include!(concat!(env!("OUT_DIR"), "/theme.rs")); // Temas registrados. static THEMES: Lazy>> = Lazy::new(|| { - RwLock::new(vec![ - &base::theme::aliner::AlinerTheme, - &base::theme::minimal::MinimalTheme, - &base::theme::bootsier::BootsierTheme, - ]) + RwLock::new(Vec::new()) }); pub fn register_theme(theme: &'static dyn ThemeTrait) { let mut themes = THEMES.write().unwrap(); - match themes.iter().find(|t| t.name() == theme.name()) { - None => { - trace::info!("{}", theme.name()); - themes.push(theme); - }, - Some(_) => {}, + if !themes.iter().any(|t| t.name() == theme.name()) { + trace::debug!("Registering \"{}\" theme", theme.name()); + themes.push(theme); } } diff --git a/pagetop/src/theme/definition.rs b/pagetop/src/theme/definition.rs index 727df347..733736e2 100644 --- a/pagetop/src/theme/definition.rs +++ b/pagetop/src/theme/definition.rs @@ -1,7 +1,7 @@ use crate::app; use crate::config::SETTINGS; use crate::html::{Markup, html}; -use crate::response::page::{Page, PageAssets, PageComponent}; +use crate::response::page::{Favicon, Page, PageAssets, PageComponent}; use crate::base::component::Chunck; /// Los temas deben implementar este "trait". @@ -20,6 +20,11 @@ pub trait ThemeTrait: Send + Sync { #[allow(unused_variables)] fn before_render_page(&self, page: &mut Page) { + page.assets() + .with_favicon( + Favicon::new() + .with_icon("/theme/favicon.png") + ); } fn render_page_head(&self, page: &mut Page) -> Markup { diff --git a/pagetop/src/util.rs b/pagetop/src/util.rs index c5254618..4ba4f76e 100644 --- a/pagetop/src/util.rs +++ b/pagetop/src/util.rs @@ -37,24 +37,24 @@ macro_rules! theme_static_files { } pub fn valid_id(id: &str) -> Option { - let id = id.trim().replace(" ", "_").to_lowercase(); + let id = id.trim(); match id.is_empty() { true => None, - false => Some(id), + false => Some(id.replace(" ", "_").to_lowercase()), } } -pub fn optional_str(s: &str) -> Option { - let s = s.to_owned(); +pub fn valid_str(s: &str) -> Option { + let s = s.trim(); match s.is_empty() { true => None, - false => Some(s), + false => Some(s.to_owned()), } } pub fn assigned_str(optional: &Option) -> &str { match optional { Some(o) => o.as_str(), - _ => "", + None => "", } } diff --git a/pagetop/static/theme/favicon.png b/pagetop/static/theme/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..7d38ea4a847b5697db4437972cfc28baba5e05e3 GIT binary patch literal 11471 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