Integra la rama 'main' en 'actix-v4'

This commit is contained in:
Manuel Cillero 2022-07-02 13:23:12 +02:00
commit cafc4422fa
149 changed files with 7497 additions and 2100 deletions

View file

@ -1,20 +1,18 @@
[package]
name = "pagetop"
version = "0.0.1"
version = "0.0.16"
edition = "2021"
authors = [
"Manuel Cillero <manuel@cillero.es>"
]
description = """\
PageTop es un proyecto personal para aprender Rust. Incluye algunos de los \
crates más estables y populares para desarrollar soluciones web modulares, \
extensibles y configurables. También es un sistema para la gestión de \
contenidos web.\
PageTop is an opinionated framework for using the most stable and popular \
Rust packages to build modular, extensible and configurable web solutions.\
"""
homepage = "https://suitepro.cillero.es/projects/pagetop"
repository = "https://gitlab.com/manuelcillero/pagetop"
license = "MIT"
license = "Apache-2.0 or MIT"
keywords = [
"web", "cms", "framework", "frontend", "ssr"
@ -24,53 +22,56 @@ categories = [
]
[dependencies]
doc-comment = "0.3.3"
downcast-rs = "1.2.0"
figlet-rs = "0.1.3"
futures = "0.3.21"
once_cell = "1.9.0"
url = "2.2.2"
concat-string = "1.0.1"
doc-comment = "0.3.3"
figlet-rs = "0.1.3"
futures = "0.3.21"
once_cell = "1.12.0"
substring = "1.4.5"
term_size = "0.3.2"
url = "2.2.2"
config_rs = { package = "config", version = "0.11.0", features = ["toml"] }
tracing = "0.1.32"
tracing-appender = "0.2.1"
tracing-subscriber = { version = "0.3.9", features = ["json", "env-filter"] }
tracing = "0.1.35"
tracing-appender = "0.2.2"
tracing-subscriber = { version = "0.3.11", features = ["json", "env-filter"] }
tracing-unwrap = { version = "0.9.2", default-features = false }
tracing-actix-web = "0.5.1"
tracing-actix-web = "0.6.0"
fluent-templates = "0.6.1"
fluent-templates = "0.7.1"
unic-langid = "0.9.0"
actix-web = "4.0.1"
actix-web = "4.1.0"
actix-web-static-files = "4.0.0"
static-files = "0.2.3"
actix-files = "0.6.1"
maud = { version = "0.23.0", features = ["actix-web"] }
maud = { git = "https://github.com/lambda-fairy/maud", rev = "e6787cd62165a075c7f16a32f8bbacc398f52d13", features = ["actix-web"] }
sycamore = { version = "0.7.1", features = ["ssr"] }
serde = { version = "1.0", features = ["derive"] }
[dependencies.sea-orm]
version = "0.6.0"
version = "0.8.0"
features = ["debug-print", "macros", "runtime-async-std-native-tls"]
default-features = false
optional = true
[dependencies.sea-schema]
version = "0.6.0"
features = ["debug-print", "migration"]
default-features = false
[dependencies.sea-orm-migration]
version = "0.8.3"
optional = true
[features]
default = []
mysql = ["sea-orm", "sea-schema", "sea-orm/sqlx-mysql"]
postgres = ["sea-orm", "sea-schema", "sea-orm/sqlx-postgres"]
sqlite = ["sea-orm", "sea-schema", "sea-orm/sqlx-sqlite"]
mysql = ["sea-orm", "sea-orm-migration", "sea-orm/sqlx-mysql"]
postgres = ["sea-orm", "sea-orm-migration", "sea-orm/sqlx-postgres"]
sqlite = ["sea-orm", "sea-orm-migration", "sea-orm/sqlx-sqlite"]
[build-dependencies]
static-files = "0.2.3"
[dev-dependencies]
tokio = { version = "1.17.0", features = ["macros", "rt-multi-thread"] }
[lib]
name = "pagetop"

View file

@ -8,19 +8,18 @@ edition = "2021"
[dependencies.pagetop]
path = "../pagetop"
# Opcional. Por defecto se puede usar PageTop sin base de datos.
# Si se usa base de datos:
features = ["mysql"]
# features = ["postgres"]
# features = ["sqlite"]
# PageTop puede dar soporte a todas las bases de datos.
# Soporte alternativo a todas las bases de datos:
# features = ["mysql", "postgres", "sqlite"]
# Sólo cuando no se usen las características predeterminadas.
# En estos casos hay que deshabilitar las características predeterminadas:
default-features = false
[dependencies]
# Requerido.
actix-web = "3.3.3"
# Opcional. Sólo si se usa la macro html!.
# Si se usa la macro html!:
maud = { version = "0.23.0" }
# Opcional. Si se requiere serialización de estructuras de datos.
# Si se requiere serialización de estructuras de datos:
serde = { version = "1.0", features = ["derive"] }

View file

@ -0,0 +1,14 @@
[package]
name = "app"
version = "0.1.0"
edition = "2021"
# Ver más claves y sus definiciones en
# https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
pagetop = { path = "../pagetop" }
# Si se usa la macro html!:
maud = { version = "0.23.0" }
# Si se requiere serialización de estructuras de datos:
serde = { version = "1.0", features = ["derive"] }

View file

@ -8,7 +8,7 @@ language = "en-US"
# Dirección predeterminada para el texto: "ltr", "rtl" o "auto".
direction = "ltr"
# Rótulo al inicio: "Off", "Slant", "Small", "Speed" o "Starwars".
startup_banner = "Small"
startup_banner = "Slant"
[log]
# Traza de ejecución: "Error", "Warn", "Info", "Debug" o "Trace".
@ -25,7 +25,7 @@ format = "Full"
[database]
# Conecta con una base de datos (opcional).
# Tipo de la base de datos (mysql, postgres ó sqlite).
# Tipo de base de datos (mysql, postgres ó sqlite).
db_type = ""
# Nombre (para mysql/postgres) o referencia (para sqlite) de la base de datos.
db_name = ""
@ -43,3 +43,11 @@ max_pool_size = 5
# Configuración del servidor web.
bind_address = "localhost"
bind_port = 8088
[dev]
# Los archivos estáticos requeridos por temas y componentes incluidos en PageTop
# se integran de manera predeterminada en el binario ejecutable. Sin embargo, es
# útil servir estos archivos desde su propio directorio durante el desarrollo ya
# que no requiere compilar cada vez que se modifican. En este caso, normalmente,
# basta con indicar el directorio "pagetop/static".
static_files = ""

View file

@ -2,6 +2,8 @@ pub use actix_web::{
App, HttpRequest, HttpResponse, HttpServer, Responder, Result, http, web
};
mod banner;
mod tracing;
pub mod locale;
@ -9,4 +11,7 @@ pub mod locale;
#[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))]
pub mod db;
pub mod app;
mod definition;
pub use definition::AppTrait;
pub mod application;

View file

@ -0,0 +1,72 @@
use crate::{Lazy, base, trace};
use crate::config::SETTINGS;
use crate::core::{module, theme};
use super::AppTrait;
use std::io::Error;
use actix_web::middleware::normalize::{NormalizePath, TrailingSlash};
use actix_web::dev::Server;
pub struct Application {
server: Server,
}
impl Application {
pub async fn prepare(app: impl AppTrait) -> Result<Self, Error> {
// Rótulo de presentación.
super::banner::print_on_startup();
// Inicia registro de trazas y eventos.
Lazy::force(&super::tracing::TRACING);
// Valida el identificador de idioma.
Lazy::force(&super::locale::LANGID);
// Conecta con la base de datos (opcional).
#[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))]
Lazy::force(&super::db::DBCONN);
// Habilita los módulos de la aplicación.
module::all::enable_modules(app.enable_modules());
// Registra los temas predeterminados.
theme::all::register_themes(vec![
&base::theme::aliner::Aliner,
&base::theme::minimal::Minimal,
&base::theme::bootsier::Bootsier,
]);
// Registra los temas de la aplicación.
theme::all::register_themes(app.themes());
// Registra las acciones de todos los módulos.
module::all::register_hooks();
// Ejecuta actualizaciones pendientes de la base de datos (opcional).
#[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))]
module::all::run_migrations();
// Ejecuta la función de inicio de la aplicación.
trace::info!("Calling application bootstrap");
app.bootstrap();
// Prepara el servidor web.
let server = super::HttpServer::new(move || {
super::App::new()
.wrap(tracing_actix_web::TracingLogger)
.wrap(NormalizePath::new(TrailingSlash::Trim))
.configure(&module::all::modules)
.configure(&theme::all::themes)
})
.bind(format!("{}:{}",
&SETTINGS.webserver.bind_address,
&SETTINGS.webserver.bind_port
))?
.run();
Ok(Self { server })
}
pub fn run(self) -> Result<Server, Error> {
Ok(self.server)
}
}

31
pagetop/src/app/banner.rs Normal file
View file

@ -0,0 +1,31 @@
mod figfont;
use figfont::FIGFONT;
use crate::config::SETTINGS;
use substring::Substring;
pub fn print_on_startup() {
if SETTINGS.app.startup_banner.to_lowercase() != "off" {
if let Some((term_width, _)) = term_size::dimensions() {
if term_width >= 80 {
let maxlen = (term_width / 10) - 2;
let mut app = SETTINGS.app.name.substring(0, maxlen).to_owned();
if SETTINGS.app.name.len() > maxlen {
app = format!("{}...", app);
}
println!("\n{} {}\n\n Powered by PageTop {}\n",
FIGFONT.convert(&app).unwrap(),
&SETTINGS.app.description,
env!("CARGO_PKG_VERSION")
);
return;
}
}
println!("\n{}\n{}\n\nPowered by PageTop {}\n",
&SETTINGS.app.name,
&SETTINGS.app.description,
env!("CARGO_PKG_VERSION")
);
}
}

View file

@ -0,0 +1,30 @@
use crate::Lazy;
use crate::config::SETTINGS;
use figlet_rs::FIGfont;
pub static FIGFONT: Lazy<FIGfont> = Lazy::new(|| {
let slant = include_str!("slant.flf");
let small = include_str!("small.flf");
let speed = include_str!("speed.flf");
let starwars = include_str!("starwars.flf");
FIGfont::from_content(
match SETTINGS.app.startup_banner.to_lowercase().as_str() {
"off" => slant,
"slant" => slant,
"small" => small,
"speed" => speed,
"starwars" => starwars,
_ => {
println!(
"\n FIGfont \"{}\" not found for banner. {}. {}.",
SETTINGS.app.startup_banner,
"Using \"Slant\"",
"Check the settings file",
);
slant
}
}
).unwrap()
});

89
pagetop/src/app/db.rs Normal file
View file

@ -0,0 +1,89 @@
use crate::{Lazy, run_now, trace};
use crate::config::SETTINGS;
use crate::db::*;
use sea_orm::{ConnectionTrait, ConnectOptions, Database, DatabaseBackend, Statement};
use tracing_unwrap::ResultExt;
pub static DBCONN: Lazy<DbConn> = Lazy::new(|| {
trace::info!(
"Connecting to database \"{}\" using a pool of {} connections",
&SETTINGS.database.db_name,
&SETTINGS.database.max_pool_size
);
let db_uri = match SETTINGS.database.db_type.as_str() {
"mysql" | "postgres" => {
let mut tmp_uri = DbUri::parse(format!(
"{}://{}/{}",
&SETTINGS.database.db_type,
&SETTINGS.database.db_host,
&SETTINGS.database.db_name
).as_str()).unwrap();
tmp_uri.set_username(
&SETTINGS.database.db_user.as_str()
).unwrap();
// https://github.com/launchbadge/sqlx/issues/1624
tmp_uri.set_password(
Some(&SETTINGS.database.db_pass.as_str())
).unwrap();
if SETTINGS.database.db_port != 0 {
tmp_uri.set_port(
Some(SETTINGS.database.db_port)
).unwrap();
}
tmp_uri
},
"sqlite" => DbUri::parse(
format!("{}://{}",
&SETTINGS.database.db_type,
&SETTINGS.database.db_name
).as_str()).unwrap(),
_ => {
trace::error!(
"Unrecognized database type \"{}\"",
&SETTINGS.database.db_type
);
DbUri::parse("").unwrap()
}
};
run_now(
Database::connect::<ConnectOptions>({
let mut db_opt = ConnectOptions::new(db_uri.to_string());
db_opt.max_connections(SETTINGS.database.max_pool_size);
db_opt.into()
})
).expect_or_log("Failed to connect to database")
});
static DBBACKEND: Lazy<DatabaseBackend> = Lazy::new(|| {
DBCONN.get_database_backend()
});
pub async fn query<Q: QueryStatementWriter>(stmt: &mut Q) -> Result<Vec<QueryResult>, DbErr> {
DBCONN.query_all(Statement::from_string(
*DBBACKEND,
match *DBBACKEND {
DatabaseBackend::MySql => stmt.to_string(MysqlQueryBuilder),
DatabaseBackend::Postgres => stmt.to_string(PostgresQueryBuilder),
DatabaseBackend::Sqlite => stmt.to_string(SqliteQueryBuilder),
}
)).await
}
pub async fn exec<Q: QueryStatementWriter>(stmt: &mut Q) -> Result<Option<QueryResult>, DbErr> {
DBCONN.query_one(Statement::from_string(
*DBBACKEND,
match *DBBACKEND {
DatabaseBackend::MySql => stmt.to_string(MysqlQueryBuilder),
DatabaseBackend::Postgres => stmt.to_string(PostgresQueryBuilder),
DatabaseBackend::Sqlite => stmt.to_string(SqliteQueryBuilder),
}
)).await
}
pub async fn exec_raw(stmt: String) -> Result<ExecResult, DbErr> {
DBCONN.execute(Statement::from_string(*DBBACKEND, stmt)).await
}

View file

@ -0,0 +1,22 @@
use crate::base::module::demopage;
use crate::core::module::ModuleTrait;
use crate::core::theme::ThemeTrait;
pub trait AppTrait: Send + Sync {
fn bootstrap(&self) {
}
fn enable_modules(&self) -> Vec<&'static dyn ModuleTrait> {
vec![
&demopage::Demopage,
]
}
fn disable_modules(&self) -> Vec<&'static dyn ModuleTrait> {
vec![]
}
fn themes(&self) -> Vec<&'static dyn ThemeTrait> {
vec![]
}
}

View file

@ -11,11 +11,12 @@ pub static LANGID: Lazy<LanguageIdentifier> = Lazy::new(|| {
Ok(language) => language,
Err(_) => {
trace::warn!(
"Failed to parse language \"{}\". {}. {}. {}.",
"{}, {} \"{}\"! {}, {}",
"Failed to parse language",
"unrecognized Unicode Language Identifier",
SETTINGS.app.language,
"Unrecognized Unicode Language Identifier",
"Using \"en-US\"",
"Check the settings file",
"check the settings file",
);
"en-US".parse().unwrap()
}

View file

@ -1,5 +1,3 @@
//! Temas, Módulos y Componentes base.
pub mod theme;
pub mod module;
pub mod component;
pub mod module;
pub mod theme;

View file

@ -0,0 +1,44 @@
mod container;
pub use container::{
CONTAINER_COMPONENT, Container, ContainerType
};
pub mod grid;
mod chunck;
pub use chunck::{
CHUNCK_COMPONENT, Chunck
};
mod icon;
pub use icon::{
ICON_COMPONENT, Icon
};
mod heading;
pub use heading::{
HEADING_COMPONENT, Heading, HeadingDisplay, HeadingType
};
mod paragraph;
pub use paragraph::{
PARAGRAPH_COMPONENT, Paragraph, ParagraphDisplay
};
mod anchor;
pub use anchor::{
ANCHOR_COMPONENT, Anchor, AnchorIcon, AnchorTarget, AnchorType
};
mod block;
pub use block::{
BLOCK_COMPONENT, Block
};
mod image;
pub use image::{
IMAGE_COMPONENT, Image
};
mod menu;
pub use menu::{
MENU_COMPONENT, MENUITEM_COMPONENT, Menu, MenuItem, MenuItemType
};
pub mod form;
pub use form::{
FORM_COMPONENT, Form, FormMethod
};

View file

@ -0,0 +1,265 @@
use crate::prelude::*;
pub const ANCHOR_COMPONENT: &str = "pagetop::component::anchor";
pub enum AnchorType {
Button,
Link,
Location,
}
pub enum AnchorTarget {
Blank,
Context(String),
Default,
Parent,
Top,
}
pub type AnchorIcon = ComponentsBundle;
pub struct Anchor {
renderable : fn() -> bool,
weight : isize,
anchor_type: AnchorType,
href : OptAttr,
html : Markup,
left_icon : AnchorIcon,
right_icon : AnchorIcon,
target : AnchorTarget,
id : OptIden,
classes : Classes,
template : String,
}
impl ComponentTrait for Anchor {
fn new() -> Self {
Anchor {
renderable : render_always,
weight : 0,
anchor_type: AnchorType::Link,
href : OptAttr::new(),
html : html! {},
left_icon : AnchorIcon::new(),
right_icon : AnchorIcon::new(),
target : AnchorTarget::Default,
id : OptIden::new(),
classes : Classes::new(),
template : "default".to_owned(),
}
}
fn handler(&self) -> &'static str {
ANCHOR_COMPONENT
}
fn is_renderable(&self) -> bool {
(self.renderable)()
}
fn weight(&self) -> isize {
self.weight
}
fn default_render(&self, context: &mut InContext) -> Markup {
let target = match &self.target() {
AnchorTarget::Blank => Some("_blank"),
AnchorTarget::Context(name) => Some(name.as_str()),
AnchorTarget::Parent => Some("_parent"),
AnchorTarget::Top => Some("_top"),
_ => None,
};
html! {
a
id=[self.id()]
class=[self.classes()]
href=[self.href()]
target=[target]
{
(self.left_icon().render(context))
(" ")(*self.html())(" ")
(self.right_icon().render(context))
}
}
}
fn as_ref_any(&self) -> &dyn AnyComponent {
self
}
fn as_mut_any(&mut self) -> &mut dyn AnyComponent {
self
}
}
impl Anchor {
pub fn link(href: &str, html: Markup) -> Self {
Anchor::new().with_href(href).with_html(html)
}
pub fn button(href: &str, html: Markup) -> Self {
Anchor::new().with_type(AnchorType::Button).with_href(href).with_html(html)
}
pub fn location(id: &str) -> Self {
Anchor::new().with_type(AnchorType::Location).with_id(id)
}
// Anchor BUILDER.
pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self {
self.alter_renderable(renderable);
self
}
pub fn with_weight(mut self, weight: isize) -> Self {
self.alter_weight(weight);
self
}
pub fn with_type(mut self, anchor_type: AnchorType) -> Self {
self.alter_type(anchor_type);
self
}
pub fn with_href(mut self, href: &str) -> Self {
self.alter_href(href);
self
}
pub fn with_html(mut self, html: Markup) -> Self {
self.alter_html(html);
self
}
pub fn with_left_icon(mut self, icon: Icon) -> Self {
self.alter_left_icon(icon);
self
}
pub fn with_right_icon(mut self, icon: Icon) -> Self {
self.alter_right_icon(icon);
self
}
pub fn with_target(mut self, target: AnchorTarget) -> Self {
self.alter_target(target);
self
}
pub fn with_id(mut self, id: &str) -> Self {
self.alter_id(id);
self
}
pub fn with_classes(mut self, classes: &str, op: ClassesOp) -> Self {
self.alter_classes(classes, op);
self
}
pub fn using_template(mut self, template: &str) -> Self {
self.alter_template(template);
self
}
// Anchor ALTER.
pub fn alter_renderable(&mut self, renderable: fn() -> bool) -> &mut Self {
self.renderable = renderable;
self
}
pub fn alter_weight(&mut self, weight: isize) -> &mut Self {
self.weight = weight;
self
}
pub fn alter_type(&mut self, anchor_type: AnchorType) -> &mut Self {
self.anchor_type = anchor_type;
self.classes.alter(match self.anchor_type {
AnchorType::Button => "btn btn-primary",
_ => "",
}, ClassesOp::SetDefault);
self
}
pub fn alter_href(&mut self, href: &str) -> &mut Self {
self.href.with_value(href);
self
}
pub fn alter_html(&mut self, html: Markup) -> &mut Self {
self.html = html;
self
}
pub fn alter_left_icon(&mut self, icon: Icon) -> &mut Self {
self.left_icon.clear();
self.left_icon.add(icon);
self
}
pub fn alter_right_icon(&mut self, icon: Icon) -> &mut Self {
self.right_icon.clear();
self.right_icon.add(icon);
self
}
pub fn alter_target(&mut self, target: AnchorTarget) -> &mut Self {
self.target = target;
self
}
pub fn alter_id(&mut self, id: &str) -> &mut Self {
self.id.with_value(id);
self
}
pub fn alter_classes(&mut self, classes: &str, op: ClassesOp) -> &mut Self {
self.classes.alter(classes, op);
self
}
pub fn alter_template(&mut self, template: &str) -> &mut Self {
self.template = template.to_owned();
self
}
// Anchor GETTERS.
pub fn anchor_type(&self) -> &AnchorType {
&self.anchor_type
}
pub fn href(&self) -> &Option<String> {
self.href.option()
}
pub fn html(&self) -> &Markup {
&self.html
}
pub fn left_icon(&self) -> &AnchorIcon {
&self.left_icon
}
pub fn right_icon(&self) -> &AnchorIcon {
&self.right_icon
}
pub fn target(&self) -> &AnchorTarget {
&self.target
}
pub fn id(&self) -> &Option<String> {
self.id.option()
}
pub fn classes(&self) -> &Option<String> {
self.classes.option()
}
pub fn template(&self) -> &str {
self.template.as_str()
}
}

View file

@ -1,105 +1,158 @@
use crate::prelude::*;
pub const BLOCK_COMPONENT: &str = "pagetop::component::block";
pub struct Block {
renderable: fn() -> bool,
weight : i8,
id : Option<String>,
title : Option<String>,
markup : Vec<Markup>,
weight : isize,
components: ComponentsBundle,
title : OptAttr,
id : OptIden,
classes : Classes,
template : String,
}
impl PageComponent for Block {
fn prepare() -> Self {
impl ComponentTrait for Block {
fn new() -> Self {
Block {
renderable: always,
renderable: render_always,
weight : 0,
id : None,
title : None,
markup : Vec::new(),
components: ComponentsBundle::new(),
title : OptAttr::new(),
id : OptIden::new(),
classes : Classes::new_with_default("block"),
template : "default".to_owned(),
}
}
fn handler(&self) -> &'static str {
BLOCK_COMPONENT
}
fn is_renderable(&self) -> bool {
(self.renderable)()
}
fn weight(&self) -> i8 {
fn weight(&self) -> isize {
self.weight
}
fn default_render(&self, assets: &mut PageAssets) -> Markup {
let id = assets.serial_id(self.name(), self.id());
fn default_render(&self, context: &mut InContext) -> Markup {
let id = context.required_id::<Block>(self.id());
html! {
div id=(id) class="block" {
@if self.title != None {
h2 class="block-title" { (self.title()) }
div id=(id) class=[self.classes()] {
@match self.title() {
Some(title) => h2 class="block-title" { (title) },
None => {}
}
div class="block-body" {
@for markup in self.markup.iter() {
(*markup)
}
(self.components().render(context))
}
}
}
}
fn as_ref_any(&self) -> &dyn AnyComponent {
self
}
fn as_mut_any(&mut self) -> &mut dyn AnyComponent {
self
}
}
impl Block {
pub fn markup(markup: Markup) -> Self {
Block::prepare().add_markup(markup)
// Block CONTAINER.
pub fn add(mut self, component: impl ComponentTrait) -> Self {
self.components.add(component);
self
}
pub fn components(&self) -> &ComponentsBundle {
&self.components
}
// Block BUILDER.
pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self {
self.renderable = renderable;
self.alter_renderable(renderable);
self
}
pub fn with_weight(mut self, weight: i8) -> Self {
self.weight = weight;
self
}
pub fn with_id(mut self, id: &str) -> Self {
self.id = util::valid_id(id);
pub fn with_weight(mut self, weight: isize) -> Self {
self.alter_weight(weight);
self
}
pub fn with_title(mut self, title: &str) -> Self {
self.title = util::optional_str(title);
self.alter_title(title);
self
}
pub fn add_markup(mut self, markup: Markup) -> Self {
self.markup.push(markup);
pub fn with_id(mut self, id: &str) -> Self {
self.alter_id(id);
self
}
pub fn with_classes(mut self, classes: &str, op: ClassesOp) -> Self {
self.alter_classes(classes, op);
self
}
pub fn using_template(mut self, template: &str) -> Self {
self.alter_template(template);
self
}
// Block ALTER.
pub fn alter_renderable(&mut self, renderable: fn() -> bool) -> &mut Self {
self.renderable = renderable;
self
}
pub fn alter_weight(&mut self, weight: isize) -> &mut Self {
self.weight = weight;
self
}
pub fn alter_title(&mut self, title: &str) -> &mut Self {
self.title.with_value(title);
self
}
pub fn alter_id(&mut self, id: &str) -> &mut Self {
self.id.with_value(id);
self
}
pub fn alter_classes(&mut self, classes: &str, op: ClassesOp) -> &mut Self {
self.classes.alter(classes, op);
self
}
pub fn alter_template(&mut self, template: &str) -> &mut Self {
self.template = template.to_owned();
self
}
// Block GETTERS.
pub fn id(&self) -> &str {
util::assigned_str(&self.id)
pub fn title(&self) -> &Option<String> {
self.title.option()
}
pub fn title(&self) -> &str {
util::assigned_str(&self.title)
pub fn id(&self) -> &Option<String> {
self.id.option()
}
pub fn classes(&self) -> &Option<String> {
self.classes.option()
}
pub fn template(&self) -> &str {
self.template.as_str()
}
}
fn always() -> bool {
true
}

View file

@ -1,75 +1,105 @@
use crate::prelude::*;
pub const CHUNCK_COMPONENT: &str = "pagetop::component::chunck";
pub struct Chunck {
renderable: fn() -> bool,
weight : i8,
markup : Vec<Markup>,
weight : isize,
html : Markup,
template : String,
}
impl PageComponent for Chunck {
fn prepare() -> Self {
impl ComponentTrait for Chunck {
fn new() -> Self {
Chunck {
renderable: always,
renderable: render_always,
weight : 0,
markup : Vec::new(),
html : html! {},
template : "default".to_owned(),
}
}
fn handler(&self) -> &'static str {
CHUNCK_COMPONENT
}
fn is_renderable(&self) -> bool {
(self.renderable)()
}
fn weight(&self) -> i8 {
fn weight(&self) -> isize {
self.weight
}
fn default_render(&self, _: &mut PageAssets) -> Markup {
html! {
@for markup in self.markup.iter() {
(*markup)
}
}
fn default_render(&self, _: &mut InContext) -> Markup {
html! { (*self.html()) }
}
fn as_ref_any(&self) -> &dyn AnyComponent {
self
}
fn as_mut_any(&mut self) -> &mut dyn AnyComponent {
self
}
}
impl Chunck {
pub fn markup(markup: Markup) -> Self {
Chunck::prepare().add_markup(markup)
pub fn with(html: Markup) -> Self {
Chunck::new().with_html(html)
}
// Chunck BUILDER.
pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self {
self.renderable = renderable;
self.alter_renderable(renderable);
self
}
pub fn with_weight(mut self, weight: i8) -> Self {
self.weight = weight;
pub fn with_weight(mut self, weight: isize) -> Self {
self.alter_weight(weight);
self
}
pub fn add_markup(mut self, markup: Markup) -> Self {
self.markup.push(markup);
pub fn with_html(mut self, html: Markup) -> Self {
self.alter_html(html);
self
}
pub fn using_template(mut self, template: &str) -> Self {
self.alter_template(template);
self
}
// Chunck ALTER.
pub fn alter_renderable(&mut self, renderable: fn() -> bool) -> &mut Self {
self.renderable = renderable;
self
}
pub fn alter_weight(&mut self, weight: isize) -> &mut Self {
self.weight = weight;
self
}
pub fn alter_html(&mut self, html: Markup) -> &mut Self {
self.html = html;
self
}
pub fn alter_template(&mut self, template: &str) -> &mut Self {
self.template = template.to_owned();
self
}
// Chunck GETTERS.
pub fn html(&self) -> &Markup {
&self.html
}
pub fn template(&self) -> &str {
self.template.as_str()
}
}
fn always() -> bool {
true
}

View file

@ -1,103 +1,212 @@
use crate::prelude::*;
enum ContainerType { Column, Row, Wrapper }
pub const CONTAINER_COMPONENT: &str = "pagetop::component::container";
pub enum ContainerType { Header, Footer, Main, Section, Wrapper }
pub struct Container {
renderable: fn() -> bool,
weight : i8,
id : Option<String>,
container : ContainerType,
components: PageContainer,
template : String,
renderable : fn() -> bool,
weight : isize,
components : ComponentsBundle,
container : ContainerType,
id : OptIden,
classes : Classes,
inner_classes: Classes,
template : String,
}
impl PageComponent for Container {
fn prepare() -> Self {
impl ComponentTrait for Container {
fn new() -> Self {
Container {
renderable: always,
weight : 0,
id : None,
container : ContainerType::Wrapper,
components: PageContainer::new(),
template : "default".to_owned(),
renderable : render_always,
weight : 0,
components : ComponentsBundle::new(),
container : ContainerType::Wrapper,
id : OptIden::new(),
classes : Classes::new_with_default("container"),
inner_classes: Classes::new_with_default("container"),
template : "default".to_owned(),
}
}
fn handler(&self) -> &'static str {
CONTAINER_COMPONENT
}
fn is_renderable(&self) -> bool {
(self.renderable)()
}
fn weight(&self) -> i8 {
fn weight(&self) -> isize {
self.weight
}
fn default_render(&self, assets: &mut PageAssets) -> Markup {
let classes = match self.container {
ContainerType::Wrapper => "container",
ContainerType::Row => "row",
ContainerType::Column => "col",
};
html! {
div id=[&self.id] class=(classes) {
(self.components.render(assets))
fn default_render(&self, context: &mut InContext) -> Markup {
match self.container_type() {
ContainerType::Header => html! {
header id=[self.id()] class=[self.classes()] {
div class=[self.inner_classes()] {
(self.components().render(context))
}
}
},
ContainerType::Footer => html! {
footer id=[self.id()] class=[self.classes()] {
div class=[self.inner_classes()] {
(self.components().render(context))
}
}
},
ContainerType::Main => html! {
main id=[self.id()] class=[self.classes()] {
div class=[self.inner_classes()] {
(self.components().render(context))
}
}
},
ContainerType::Section => html! {
section id=[self.id()] class=[self.classes()] {
div class=[self.inner_classes()] {
(self.components().render(context))
}
}
},
_ => html! {
div id=[self.id()] class=[self.classes()] {
(self.components().render(context))
}
}
}
}
fn as_ref_any(&self) -> &dyn AnyComponent {
self
}
fn as_mut_any(&mut self) -> &mut dyn AnyComponent {
self
}
}
impl Container {
pub fn row() -> Self {
let mut grid = Container::prepare();
grid.container = ContainerType::Row;
grid
pub fn header() -> Self {
let mut c = Container::new().with_classes("header", ClassesOp::SetDefault);
c.container = ContainerType::Header;
c
}
pub fn column() -> Self {
let mut grid = Container::prepare();
grid.container = ContainerType::Column;
grid
pub fn footer() -> Self {
let mut c = Container::new().with_classes("footer", ClassesOp::SetDefault);
c.container = ContainerType::Footer;
c
}
pub fn main() -> Self {
let mut c = Container::new().with_classes("main", ClassesOp::SetDefault);
c.container = ContainerType::Main;
c
}
pub fn section() -> Self {
let mut c = Container::new().with_classes("section", ClassesOp::SetDefault);
c.container = ContainerType::Section;
c
}
// Container CONTAINER.
pub fn add(mut self, component: impl ComponentTrait) -> Self {
self.components.add(component);
self
}
pub fn components(&self) -> &ComponentsBundle {
&self.components
}
// Container BUILDER.
pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self {
self.renderable = renderable;
self.alter_renderable(renderable);
self
}
pub fn with_weight(mut self, weight: i8) -> Self {
self.weight = weight;
pub fn with_weight(mut self, weight: isize) -> Self {
self.alter_weight(weight);
self
}
pub fn with_id(mut self, id: &str) -> Self {
self.id = util::valid_id(id);
self.alter_id(id);
self
}
pub fn add(mut self, component: impl PageComponent) -> Self {
self.components.add(component);
pub fn with_classes(mut self, classes: &str, op: ClassesOp) -> Self {
self.alter_classes(classes, op);
self
}
pub fn with_inner_classes(mut self, classes: &str, op: ClassesOp) -> Self {
self.alter_inner_classes(classes, op);
self
}
pub fn using_template(mut self, template: &str) -> Self {
self.alter_template(template);
self
}
// Container ALTER.
pub fn alter_renderable(&mut self, renderable: fn() -> bool) -> &mut Self {
self.renderable = renderable;
self
}
pub fn alter_weight(&mut self, weight: isize) -> &mut Self {
self.weight = weight;
self
}
pub fn alter_id(&mut self, id: &str) -> &mut Self {
self.id.with_value(id);
self
}
pub fn alter_classes(&mut self, classes: &str, op: ClassesOp) -> &mut Self {
self.classes.alter(classes, op);
self
}
pub fn alter_inner_classes(&mut self, classes: &str, op: ClassesOp) -> &mut Self {
self.inner_classes.alter(classes, op);
self
}
pub fn alter_template(&mut self, template: &str) -> &mut Self {
self.template = template.to_owned();
self
}
// Container GETTERS.
pub fn id(&self) -> &str {
util::assigned_str(&self.id)
pub fn container_type(&self) -> &ContainerType {
&self.container
}
pub fn id(&self) -> &Option<String> {
self.id.option()
}
pub fn classes(&self) -> &Option<String> {
self.classes.option()
}
pub fn inner_classes(&self) -> &Option<String> {
self.inner_classes.option()
}
pub fn template(&self) -> &str {
self.template.as_str()
}
}
fn always() -> bool {
true
}

View file

@ -0,0 +1,21 @@
mod form;
pub use form::{
FORM_COMPONENT, Form, FormMethod
};
mod input;
pub use input::{
INPUT_COMPONENT, Input, InputType
};
mod hidden;
pub use hidden::{
HIDDEN_COMPONENT, Hidden
};
mod date;
pub use date::{
DATE_COMPONENT, Date
};
mod button;
pub use button::{
BUTTON_COMPONENT, Button, ButtonType
};

View file

@ -1,84 +1,103 @@
use crate::prelude::*;
enum ButtonType {Button, Reset, Submit}
pub const BUTTON_COMPONENT: &str = "pagetop::component::form::button";
pub enum ButtonType {Button, Reset, Submit}
pub struct Button {
renderable : fn() -> bool,
weight : i8,
weight : isize,
button_type: ButtonType,
name : Option<String>,
value : Option<String>,
autofocus : Option<String>,
disabled : Option<String>,
name : OptAttr,
value : OptAttr,
autofocus : OptAttr,
disabled : OptAttr,
classes : Classes,
template : String,
}
impl PageComponent for Button {
fn prepare() -> Self {
impl ComponentTrait for Button {
fn new() -> Self {
Button {
renderable : always,
renderable : render_always,
weight : 0,
button_type: ButtonType::Button,
name : None,
value : None,
autofocus : None,
disabled : None,
name : OptAttr::new(),
value : OptAttr::new(),
autofocus : OptAttr::new(),
disabled : OptAttr::new(),
classes : Classes::new_with_default("btn btn-primary"),
template : "default".to_owned(),
}
.with_classes("form-button", ClassesOp::AddFirst)
}
fn handler(&self) -> &'static str {
BUTTON_COMPONENT
}
fn is_renderable(&self) -> bool {
(self.renderable)()
}
fn weight(&self) -> i8 {
fn weight(&self) -> isize {
self.weight
}
fn default_render(&self, _: &mut PageAssets) -> Markup {
let (button_type, button_class) = match &self.button_type {
ButtonType::Button => ("button", "btn btn-primary form-button"),
ButtonType::Reset => ("reset", "btn btn-primary form-reset" ),
ButtonType::Submit => ("submit", "btn btn-primary form-submit")
fn default_render(&self, _: &mut InContext) -> Markup {
let button_type = match self.button_type() {
ButtonType::Button => "button",
ButtonType::Reset => "reset",
ButtonType::Submit => "submit",
};
let id_item = match &self.name {
Some(name) => Some(format!("edit-{}", name)),
let id = match self.name() {
Some(name) => Some(concat_string!("edit-", name)),
_ => None
};
html! {
button
type=(button_type)
id=[&id_item]
class=(button_class)
name=[&self.name]
value=[&self.value]
autofocus=[&self.autofocus]
disabled=[&self.disabled]
id=[id]
class=[self.classes()]
name=[self.name()]
value=[self.value()]
autofocus=[self.autofocus()]
disabled=[self.disabled()]
{
@match &self.value {
Some(value) => (value),
_ => ""
};
@match self.value() {
Some(value) => { (value) },
None => {},
}
}
}
}
fn as_ref_any(&self) -> &dyn AnyComponent {
self
}
fn as_mut_any(&mut self) -> &mut dyn AnyComponent {
self
}
}
impl Button {
pub fn button(value: &str) -> Self {
Button::prepare().with_value(value)
Button::new().with_value(value)
}
pub fn reset(value: &str) -> Self {
let mut button = Button::prepare().with_value(value);
let mut button = Button::new()
.with_classes("form-reset", ClassesOp::Replace("form-button"))
.with_value(value);
button.button_type = ButtonType::Reset;
button
}
pub fn submit(value: &str) -> Self {
let mut button = Button::prepare().with_value(value);
let mut button = Button::new()
.with_classes("form-submit", ClassesOp::Replace("form-button"))
.with_value(value);
button.button_type = ButtonType::Submit;
button
}
@ -86,75 +105,120 @@ impl Button {
// Button BUILDER.
pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self {
self.renderable = renderable;
self.alter_renderable(renderable);
self
}
pub fn with_weight(mut self, weight: i8) -> Self {
self.weight = weight;
pub fn with_weight(mut self, weight: isize) -> Self {
self.alter_weight(weight);
self
}
pub fn with_name(mut self, name: &str) -> Self {
self.name = util::valid_id(name);
self.alter_name(name);
self
}
pub fn with_value(mut self, value: &str) -> Self {
self.value = util::optional_str(value);
self.alter_value(value);
self
}
pub fn autofocus(mut self, toggle: bool) -> Self {
self.autofocus = match toggle {
true => Some("autofocus".to_owned()),
false => None
};
pub fn with_autofocus(mut self, toggle: bool) -> Self {
self.alter_autofocus(toggle);
self
}
pub fn disabled(mut self, toggle: bool) -> Self {
self.disabled = match toggle {
true => Some("disabled".to_owned()),
false => None
};
pub fn with_disabled(mut self, toggle: bool) -> Self {
self.alter_disabled(toggle);
self
}
pub fn with_classes(mut self, classes: &str, op: ClassesOp) -> Self {
self.alter_classes(classes, op);
self
}
pub fn using_template(mut self, template: &str) -> Self {
self.alter_template(template);
self
}
// Button ALTER.
pub fn alter_renderable(&mut self, renderable: fn() -> bool) -> &mut Self {
self.renderable = renderable;
self
}
pub fn alter_weight(&mut self, weight: isize) -> &mut Self {
self.weight = weight;
self
}
pub fn alter_name(&mut self, name: &str) -> &mut Self {
self.name.with_value(name);
self
}
pub fn alter_value(&mut self, value: &str) -> &mut Self {
self.value.with_value(value);
self
}
pub fn alter_autofocus(&mut self, toggle: bool) -> &mut Self {
self.autofocus.with_value(match toggle {
true => "autofocus",
false => "",
});
self
}
pub fn alter_disabled(&mut self, toggle: bool) -> &mut Self {
self.disabled.with_value(match toggle {
true => "disabled",
false => "",
});
self
}
pub fn alter_classes(&mut self, classes: &str, op: ClassesOp) -> &mut Self {
self.classes.alter(classes, op);
self
}
pub fn alter_template(&mut self, template: &str) -> &mut Self {
self.template = template.to_owned();
self
}
// Button GETTERS.
pub fn name(&self) -> &str {
util::assigned_str(&self.name)
pub fn button_type(&self) -> &ButtonType {
&self.button_type
}
pub fn value(&self) -> &str {
util::assigned_str(&self.value)
pub fn name(&self) -> &Option<String> {
self.name.option()
}
pub fn has_autofocus(&self) -> bool {
match &self.autofocus {
Some(_) => true,
_ => false
}
pub fn value(&self) -> &Option<String> {
self.value.option()
}
pub fn is_disabled(&self) -> bool {
match &self.disabled {
Some(_) => true,
_ => false
}
pub fn autofocus(&self) -> &Option<String> {
self.autofocus.option()
}
pub fn disabled(&self) -> &Option<String> {
self.disabled.option()
}
pub fn classes(&self) -> &Option<String> {
self.classes.option()
}
pub fn template(&self) -> &str {
self.template.as_str()
}
}
fn always() -> bool {
true
}

View file

@ -1,95 +1,103 @@
use crate::prelude::*;
pub const DATE_COMPONENT: &str = "pagetop::component::form::date";
pub struct Date {
renderable : fn() -> bool,
weight : i8,
name : Option<String>,
value : Option<String>,
label : Option<String>,
placeholder : Option<String>,
autofocus : Option<String>,
autocomplete: Option<String>,
disabled : Option<String>,
readonly : Option<String>,
required : Option<String>,
help_text : Option<String>,
weight : isize,
name : OptAttr,
value : OptAttr,
label : OptAttr,
placeholder : OptAttr,
autofocus : OptAttr,
autocomplete: OptAttr,
disabled : OptAttr,
readonly : OptAttr,
required : OptAttr,
help_text : OptAttr,
classes : Classes,
template : String,
}
impl PageComponent for Date {
fn prepare() -> Self {
impl ComponentTrait for Date {
fn new() -> Self {
Date {
renderable : always,
renderable : render_always,
weight : 0,
name : None,
value : None,
label : None,
placeholder : None,
autofocus : None,
autocomplete: None,
disabled : None,
readonly : None,
required : None,
help_text : None,
name : OptAttr::new(),
value : OptAttr::new(),
label : OptAttr::new(),
placeholder : OptAttr::new(),
autofocus : OptAttr::new(),
autocomplete: OptAttr::new(),
disabled : OptAttr::new(),
readonly : OptAttr::new(),
required : OptAttr::new(),
help_text : OptAttr::new(),
classes : Classes::new_with_default("form-item"),
template : "default".to_owned(),
}
.with_classes("form-type-date", ClassesOp::AddFirst)
}
fn handler(&self) -> &'static str {
DATE_COMPONENT
}
fn is_renderable(&self) -> bool {
(self.renderable)()
}
fn weight(&self) -> i8 {
fn weight(&self) -> isize {
self.weight
}
fn default_render(&self, _: &mut PageAssets) -> Markup {
let (class_item, id_item) = match &self.name {
Some(name) => (
format!("form-item form-item-{} form-type-date", name),
Some(format!("edit-{}", name))
),
None => (
"form-item form-type-date".to_owned(),
None
)
fn default_render(&self, _: &mut InContext) -> Markup {
let id = match self.name() {
Some(name) => Some(concat_string!("edit-", name)),
None => None,
};
html! {
div class=(class_item) {
@if self.label != None {
label class="form-label" for=[&id_item] {
(self.label()) " "
@if self.required != None {
span
div class=[self.classes()] {
@match self.label() {
Some(label) => label class="form-label" for=[&id] {
(label) " "
@match self.required() {
Some(_) => span
class="form-required"
title="Este campo es obligatorio."
{
"*"
} " "
title="Este campo es obligatorio." { "*" } " ",
None => {}
}
}
},
None => {}
}
input
type="date"
id=[&id_item]
id=[id]
class="form-control"
name=[&self.name]
value=[&self.value]
placeholder=[&self.placeholder]
autofocus=[&self.autofocus]
autocomplete=[&self.autocomplete]
readonly=[&self.readonly]
required=[&self.required]
disabled=[&self.disabled];
@if self.help_text != None {
div class="form-text" {
(self.help_text())
}
name=[self.name()]
value=[self.value()]
placeholder=[self.placeholder()]
autofocus=[self.autofocus()]
autocomplete=[self.autocomplete()]
readonly=[self.readonly()]
required=[self.required()]
disabled=[self.disabled()];
@match self.help_text() {
Some(help_text) => div class="form-text" { (help_text) },
None => {}
}
}
}
}
fn as_ref_any(&self) -> &dyn AnyComponent {
self
}
fn as_mut_any(&mut self) -> &mut dyn AnyComponent {
self
}
}
impl Date {
@ -97,147 +105,209 @@ impl Date {
// Date BUILDER.
pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self {
self.renderable = renderable;
self.alter_renderable(renderable);
self
}
pub fn with_weight(mut self, weight: i8) -> Self {
self.weight = weight;
pub fn with_weight(mut self, weight: isize) -> Self {
self.alter_weight(weight);
self
}
pub fn with_name(mut self, name: &str) -> Self {
self.name = util::valid_id(name);
self.alter_name(name);
self
}
pub fn with_value(mut self, value: &str) -> Self {
self.value = util::optional_str(value);
self.alter_value(value);
self
}
pub fn with_label(mut self, label: &str) -> Self {
self.label = util::optional_str(label);
self.alter_label(label);
self
}
pub fn with_placeholder(mut self, placeholder: &str) -> Self {
self.placeholder = util::optional_str(placeholder);
self.alter_placeholder(placeholder);
self
}
pub fn autofocus(mut self, toggle: bool) -> Self {
self.autofocus = match toggle {
true => Some("autofocus".to_owned()),
false => None
};
pub fn with_autofocus(mut self, toggle: bool) -> Self {
self.alter_autofocus(toggle);
self
}
pub fn autocomplete(mut self, toggle: bool) -> Self {
self.autocomplete = match toggle {
true => None,
false => Some("off".to_owned())
};
pub fn with_autocomplete(mut self, toggle: bool) -> Self {
self.alter_autocomplete(toggle);
self
}
pub fn disabled(mut self, toggle: bool) -> Self {
self.disabled = match toggle {
true => Some("disabled".to_owned()),
false => None
};
pub fn with_disabled(mut self, toggle: bool) -> Self {
self.alter_disabled(toggle);
self
}
pub fn readonly(mut self, toggle: bool) -> Self {
self.readonly = match toggle {
true => Some("readonly".to_owned()),
false => None
};
pub fn with_readonly(mut self, toggle: bool) -> Self {
self.alter_readonly(toggle);
self
}
pub fn required(mut self, toggle: bool) -> Self {
self.required = match toggle {
true => Some("required".to_owned()),
false => None
};
pub fn with_required(mut self, toggle: bool) -> Self {
self.alter_required(toggle);
self
}
pub fn with_help_text(mut self, help_text: &str) -> Self {
self.help_text = util::optional_str(help_text);
self.alter_help_text(help_text);
self
}
pub fn with_classes(mut self, classes: &str, op: ClassesOp) -> Self {
self.alter_classes(classes, op);
self
}
pub fn using_template(mut self, template: &str) -> Self {
self.alter_template(template);
self
}
// Date ALTER.
pub fn alter_renderable(&mut self, renderable: fn() -> bool) -> &mut Self {
self.renderable = renderable;
self
}
pub fn alter_weight(&mut self, weight: isize) -> &mut Self {
self.weight = weight;
self
}
pub fn alter_name(&mut self, name: &str) -> &mut Self {
self.name.with_value(name);
self
}
pub fn alter_value(&mut self, value: &str) -> &mut Self {
self.value.with_value(value);
self
}
pub fn alter_label(&mut self, label: &str) -> &mut Self {
self.label.with_value(label);
self
}
pub fn alter_placeholder(&mut self, placeholder: &str) -> &mut Self {
self.placeholder.with_value(placeholder);
self
}
pub fn alter_autofocus(&mut self, toggle: bool) -> &mut Self {
self.autofocus.with_value(match toggle {
true => "autofocus",
false => "",
});
self
}
pub fn alter_autocomplete(&mut self, toggle: bool) -> &mut Self {
self.autocomplete.with_value(match toggle {
true => "",
false => "off",
});
self
}
pub fn alter_disabled(&mut self, toggle: bool) -> &mut Self {
self.disabled.with_value(match toggle {
true => "disabled",
false => "",
});
self
}
pub fn alter_readonly(&mut self, toggle: bool) -> &mut Self {
self.readonly.with_value(match toggle {
true => "readonly",
false => "",
});
self
}
pub fn alter_required(&mut self, toggle: bool) -> &mut Self {
self.required.with_value(match toggle {
true => "required",
false => "",
});
self
}
pub fn alter_help_text(&mut self, help_text: &str) -> &mut Self {
self.help_text.with_value(help_text);
self
}
pub fn alter_classes(&mut self, classes: &str, op: ClassesOp) -> &mut Self {
self.classes.alter(classes, op);
self
}
pub fn alter_template(&mut self, template: &str) -> &mut Self {
self.template = template.to_owned();
self
}
// Date GETTERS.
pub fn name(&self) -> &str {
util::assigned_str(&self.name)
pub fn name(&self) -> &Option<String> {
self.name.option()
}
pub fn value(&self) -> &str {
util::assigned_str(&self.value)
pub fn value(&self) -> &Option<String> {
self.value.option()
}
pub fn label(&self) -> &str {
util::assigned_str(&self.label)
pub fn label(&self) -> &Option<String> {
self.label.option()
}
pub fn placeholder(&self) -> &str {
util::assigned_str(&self.placeholder)
pub fn placeholder(&self) -> &Option<String> {
self.placeholder.option()
}
pub fn has_autofocus(&self) -> bool {
match &self.autofocus {
Some(_) => true,
_ => false
}
pub fn autofocus(&self) -> &Option<String> {
self.autofocus.option()
}
pub fn has_autocomplete(&self) -> bool {
match &self.autocomplete {
Some(_) => false,
_ => true
}
pub fn autocomplete(&self) -> &Option<String> {
self.autocomplete.option()
}
pub fn is_disabled(&self) -> bool {
match &self.disabled {
Some(_) => true,
_ => false
}
pub fn disabled(&self) -> &Option<String> {
self.disabled.option()
}
pub fn is_readonly(&self) -> bool {
match &self.readonly {
Some(_) => true,
_ => false
}
pub fn readonly(&self) -> &Option<String> {
self.readonly.option()
}
pub fn is_required(&self) -> bool {
match &self.required {
Some(_) => true,
_ => false
}
pub fn required(&self) -> &Option<String> {
self.required.option()
}
pub fn help_text(&self) -> &str {
util::assigned_str(&self.help_text)
pub fn help_text(&self) -> &Option<String> {
self.help_text.option()
}
pub fn classes(&self) -> &Option<String> {
self.classes.option()
}
pub fn template(&self) -> &str {
self.template.as_str()
}
}
fn always() -> bool {
true
}

View file

@ -1,131 +1,195 @@
use crate::prelude::*;
pub const FORM_COMPONENT: &str = "pagetop::component::form";
pub enum FormMethod {Get, Post}
pub struct Form {
renderable: fn() -> bool,
weight : i8,
id : Option<String>,
action : Option<String>,
weight : isize,
elements : ComponentsBundle,
action : OptAttr,
charset : OptAttr,
method : FormMethod,
charset : Option<String>,
elements : PageContainer,
id : OptIden,
classes : Classes,
template : String,
}
impl PageComponent for Form {
fn prepare() -> Self {
impl ComponentTrait for Form {
fn new() -> Self {
Form {
renderable: always,
renderable: render_always,
weight : 0,
id : None,
action : None,
elements : ComponentsBundle::new(),
action : OptAttr::new(),
charset : OptAttr::new_with_value("UTF-8"),
method : FormMethod::Post,
charset : Some("UTF-8".to_owned()),
elements : PageContainer::new(),
id : OptIden::new(),
classes : Classes::new_with_default("form"),
template : "default".to_owned(),
}
}
fn handler(&self) -> &'static str {
FORM_COMPONENT
}
fn is_renderable(&self) -> bool {
(self.renderable)()
}
fn weight(&self) -> i8 {
fn weight(&self) -> isize {
self.weight
}
fn default_render(&self, assets: &mut PageAssets) -> Markup {
let method = match self.method {
fn default_render(&self, context: &mut InContext) -> Markup {
let method = match self.method() {
FormMethod::Get => None,
FormMethod::Post => Some("post".to_owned())
};
html! {
form
id=[&self.id]
action=[&self.action]
id=[self.id()]
class=[self.classes()]
action=[self.action()]
method=[method]
accept-charset=[&self.charset]
accept-charset=[self.charset()]
{
div {
(self.elements.render(assets))
}
div { (self.elements().render(context)) }
}
}
}
fn as_ref_any(&self) -> &dyn AnyComponent {
self
}
fn as_mut_any(&mut self) -> &mut dyn AnyComponent {
self
}
}
impl Form {
// Form BUILDER.
// Form CONTAINER.
pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self {
self.renderable = renderable;
self
}
pub fn with_weight(mut self, weight: i8) -> Self {
self.weight = weight;
self
}
pub fn with_id(mut self, id: &str) -> Self {
self.id = util::valid_id(id);
self
}
pub fn with_action(mut self, action: &str) -> Self {
self.action = util::optional_str(action);
self
}
pub fn with_method(mut self, method: FormMethod) -> Self {
self.method = method;
self
}
pub fn with_charset(mut self, charset: &str) -> Self {
self.charset = util::optional_str(charset);
self
}
pub fn add(mut self, element: impl PageComponent) -> Self {
pub fn add(mut self, element: impl ComponentTrait) -> Self {
self.elements.add(element);
self
}
pub fn elements(&self) -> &ComponentsBundle {
&self.elements
}
// Form BUILDER.
pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self {
self.alter_renderable(renderable);
self
}
pub fn with_weight(mut self, weight: isize) -> Self {
self.alter_weight(weight);
self
}
pub fn with_action(mut self, action: &str) -> Self {
self.alter_action(action);
self
}
pub fn with_charset(mut self, charset: &str) -> Self {
self.alter_charset(charset);
self
}
pub fn with_method(mut self, method: FormMethod) -> Self {
self.alter_method(method);
self
}
pub fn with_id(mut self, id: &str) -> Self {
self.alter_id(id);
self
}
pub fn with_classes(mut self, classes: &str, op: ClassesOp) -> Self {
self.alter_classes(classes, op);
self
}
pub fn using_template(mut self, template: &str) -> Self {
self.alter_template(template);
self
}
// Form ALTER.
pub fn alter_renderable(&mut self, renderable: fn() -> bool) -> &mut Self {
self.renderable = renderable;
self
}
pub fn alter_weight(&mut self, weight: isize) -> &mut Self {
self.weight = weight;
self
}
pub fn alter_action(&mut self, action: &str) -> &mut Self {
self.action.with_value(action);
self
}
pub fn alter_charset(&mut self, charset: &str) -> &mut Self {
self.charset.with_value(charset);
self
}
pub fn alter_method(&mut self, method: FormMethod) -> &mut Self {
self.method = method;
self
}
pub fn alter_id(&mut self, id: &str) -> &mut Self {
self.id.with_value(id);
self
}
pub fn alter_classes(&mut self, classes: &str, op: ClassesOp) -> &mut Self {
self.classes.alter(classes, op);
self
}
pub fn alter_template(&mut self, template: &str) -> &mut Self {
self.template = template.to_owned();
self
}
// Form GETTERS.
pub fn id(&self) -> &str {
util::assigned_str(&self.id)
pub fn action(&self) -> &Option<String> {
self.action.option()
}
pub fn action(&self) -> &str {
util::assigned_str(&self.action)
pub fn charset(&self) -> &Option<String> {
self.charset.option()
}
pub fn method(&self) -> &str {
match &self.method {
FormMethod::Get => "get",
FormMethod::Post => "post"
}
pub fn method(&self) -> &FormMethod {
&self.method
}
pub fn charset(&self) -> &str {
util::assigned_str(&self.charset)
pub fn id(&self) -> &Option<String> {
self.id.option()
}
pub fn classes(&self) -> &Option<String> {
self.classes.option()
}
pub fn template(&self) -> &str {
self.template.as_str()
}
}
fn always() -> bool {
true
}

View file

@ -1,70 +1,95 @@
use crate::prelude::*;
pub const HIDDEN_COMPONENT: &str = "pagetop::component::form::hidden";
pub struct Hidden {
weight : i8,
name : Option<String>,
value : Option<String>,
weight: isize,
name : OptIden,
value : OptAttr,
}
impl PageComponent for Hidden {
fn prepare() -> Self {
impl ComponentTrait for Hidden {
fn new() -> Self {
Hidden {
weight : 0,
name : None,
value : None,
weight: 0,
name : OptIden::new(),
value : OptAttr::new(),
}
}
fn weight(&self) -> i8 {
fn handler(&self) -> &'static str {
HIDDEN_COMPONENT
}
fn weight(&self) -> isize {
self.weight
}
fn default_render(&self, _: &mut PageAssets) -> Markup {
let id_item = match &self.name {
Some(name) => Some(format!("value-{}", name)),
fn default_render(&self, _: &mut InContext) -> Markup {
let id = match self.name() {
Some(name) => Some(concat_string!("value-", name)),
_ => None
};
html! {
input
type="hidden"
id=[&id_item]
name=[&self.name]
value=[&self.value];
input type="hidden" id=[id] name=[self.name()] value=[self.value()];
}
}
fn as_ref_any(&self) -> &dyn AnyComponent {
self
}
fn as_mut_any(&mut self) -> &mut dyn AnyComponent {
self
}
}
impl Hidden {
pub fn set(name: &str, value: &str) -> Self {
Hidden::prepare().with_name(name).with_value(value)
Hidden::new().with_name(name).with_value(value)
}
// Hidden BUILDER.
pub fn with_weight(mut self, weight: i8) -> Self {
self.weight = weight;
pub fn with_weight(mut self, weight: isize) -> Self {
self.alter_weight(weight);
self
}
pub fn with_name(mut self, name: &str) -> Self {
self.name = util::valid_id(name);
self.alter_name(name);
self
}
pub fn with_value(mut self, value: &str) -> Self {
self.value = util::optional_str(value);
self.alter_value(value);
self
}
// Hidden ALTER.
pub fn alter_weight(&mut self, weight: isize) -> &mut Self {
self.weight = weight;
self
}
pub fn alter_name(&mut self, name: &str) -> &mut Self {
self.name.with_value(name);
self
}
pub fn alter_value(&mut self, value: &str) -> &mut Self {
self.value.with_value(value);
self
}
// Hidden GETTERS.
pub fn name(&self) -> &str {
util::assigned_str(&self.name)
pub fn name(&self) -> &Option<String> {
self.name.option()
}
pub fn value(&self) -> &str {
util::assigned_str(&self.value)
pub fn value(&self) -> &Option<String> {
self.value.option()
}
}

View file

@ -1,150 +1,162 @@
use crate::prelude::*;
enum InputType {Email, Password, Search, Telephone, Textfield, Url}
pub const INPUT_COMPONENT: &str = "pagetop::component::form::input";
pub enum InputType {Email, Password, Search, Telephone, Textfield, Url}
pub struct Input {
renderable : fn() -> bool,
weight : i8,
weight : isize,
input_type : InputType,
name : Option<String>,
value : Option<String>,
label : Option<String>,
name : OptIden,
value : OptAttr,
label : OptAttr,
size : Option<u16>,
minlength : Option<u16>,
maxlength : Option<u16>,
placeholder : Option<String>,
autofocus : Option<String>,
autocomplete: Option<String>,
disabled : Option<String>,
readonly : Option<String>,
required : Option<String>,
help_text : Option<String>,
placeholder : OptAttr,
autofocus : OptAttr,
autocomplete: OptAttr,
disabled : OptAttr,
readonly : OptAttr,
required : OptAttr,
help_text : OptAttr,
classes : Classes,
template : String,
}
impl PageComponent for Input {
fn prepare() -> Self {
impl ComponentTrait for Input {
fn new() -> Self {
Input {
renderable : always,
renderable : render_always,
weight : 0,
input_type : InputType::Textfield,
name : None,
value : None,
label : None,
name : OptIden::new(),
value : OptAttr::new(),
label : OptAttr::new(),
size : Some(60),
minlength : None,
maxlength : Some(128),
placeholder : None,
autofocus : None,
autocomplete: None,
disabled : None,
readonly : None,
required : None,
help_text : None,
placeholder : OptAttr::new(),
autofocus : OptAttr::new(),
autocomplete: OptAttr::new(),
disabled : OptAttr::new(),
readonly : OptAttr::new(),
required : OptAttr::new(),
help_text : OptAttr::new(),
classes : Classes::new_with_default("form-item"),
template : "default".to_owned(),
}
.with_classes("form-type-textfield", ClassesOp::AddFirst)
}
fn handler(&self) -> &'static str {
INPUT_COMPONENT
}
fn is_renderable(&self) -> bool {
(self.renderable)()
}
fn weight(&self) -> i8 {
fn weight(&self) -> isize {
self.weight
}
fn default_render(&self, _: &mut PageAssets) -> Markup {
let (input_type, class_type) = match &self.input_type {
InputType::Email => ("email", "form-type-email"),
InputType::Password => ("password", "form-type-password"),
InputType::Search => ("search", "form-type-search"),
InputType::Telephone => ("tel", "form-type-telephone"),
InputType::Textfield => ("text", "form-type-textfield"),
InputType::Url => ("url", "form-type-url")
fn default_render(&self, _: &mut InContext) -> Markup {
let type_input = match self.input_type() {
InputType::Email => "email",
InputType::Password => "password",
InputType::Search => "search",
InputType::Telephone => "tel",
InputType::Textfield => "text",
InputType::Url => "url",
};
let (class_item, id_item) = match &self.name {
Some(name) => (
format!("form-item form-item-{} {}", name, class_type),
Some(format!("edit-{}", name))
),
None => (
format!("form-item {}", class_type),
None
)
let id = match self.name() {
Some(name) => Some(concat_string!("edit-", name)),
None => None,
};
html! {
div class=(class_item) {
@if self.label != None {
label class="form-label" for=[&id_item] {
(self.label()) " "
@if self.required != None {
span
div class=[self.classes()] {
@match self.label() {
Some(label) => label class="form-label" for=[&id] {
(label) " "
@match self.required() {
Some(_) => span
class="form-required"
title="Este campo es obligatorio."
{
"*"
} " "
title="Este campo es obligatorio." { "*" } " ",
None => {}
}
}
},
None => {}
}
input
type=(input_type)
id=[&id_item]
type=(type_input)
id=[id]
class="form-control"
name=[&self.name]
value=[&self.value]
size=[self.size]
minlength=[self.minlength]
maxlength=[self.maxlength]
placeholder=[&self.placeholder]
autofocus=[&self.autofocus]
autocomplete=[&self.autocomplete]
readonly=[&self.readonly]
required=[&self.required]
disabled=[&self.disabled];
@if self.help_text != None {
div class="form-text" {
(self.help_text())
}
name=[self.name()]
value=[self.value()]
size=[self.size()]
minlength=[self.minlength()]
maxlength=[self.maxlength()]
placeholder=[self.placeholder()]
autofocus=[self.autofocus()]
autocomplete=[self.autocomplete()]
readonly=[self.readonly()]
required=[self.required()]
disabled=[self.disabled()];
@match self.help_text() {
Some(help_text) => div class="form-text" { (help_text) },
None => {}
}
}
}
}
fn as_ref_any(&self) -> &dyn AnyComponent {
self
}
fn as_mut_any(&mut self) -> &mut dyn AnyComponent {
self
}
}
impl Input {
pub fn textfield() -> Self {
Input::prepare()
Input::new()
}
pub fn password() -> Self {
let mut input = Input::prepare();
let mut input = Input::new()
.with_classes("form-type-password", ClassesOp::Replace("form-type-textfield"));
input.input_type = InputType::Password;
input
}
pub fn search() -> Self {
let mut input = Input::prepare();
let mut input = Input::new()
.with_classes("form-type-search", ClassesOp::Replace("form-type-textfield"));
input.input_type = InputType::Search;
input
}
pub fn email() -> Self {
let mut input = Input::prepare();
let mut input = Input::new()
.with_classes("form-type-email", ClassesOp::Replace("form-type-textfield"));
input.input_type = InputType::Email;
input
}
pub fn telephone() -> Self {
let mut input = Input::prepare();
let mut input = Input::new()
.with_classes("form-type-telephone", ClassesOp::Replace("form-type-textfield"));
input.input_type = InputType::Telephone;
input
}
pub fn url() -> Self {
let mut input = Input::prepare();
let mut input = Input::new()
.with_classes("form-type-url", ClassesOp::Replace("form-type-textfield"));
input.input_type = InputType::Url;
input
}
@ -152,112 +164,212 @@ impl Input {
// Input BUILDER.
pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self {
self.renderable = renderable;
self.alter_renderable(renderable);
self
}
pub fn with_weight(mut self, weight: i8) -> Self {
self.weight = weight;
pub fn with_weight(mut self, weight: isize) -> Self {
self.alter_weight(weight);
self
}
pub fn with_name(mut self, name: &str) -> Self {
self.name = util::valid_id(name);
self.alter_name(name);
self
}
pub fn with_value(mut self, value: &str) -> Self {
self.value = util::optional_str(value);
self.alter_value(value);
self
}
pub fn with_label(mut self, label: &str) -> Self {
self.label = util::optional_str(label);
self.alter_label(label);
self
}
pub fn with_size(mut self, size: Option<u16>) -> Self {
self.size = size;
self.alter_size(size);
self
}
pub fn with_minlength(mut self, minlength: Option<u16>) -> Self {
self.minlength = minlength;
self.alter_minlength(minlength);
self
}
pub fn with_maxlength(mut self, maxlength: Option<u16>) -> Self {
self.maxlength = maxlength;
self.alter_maxlength(maxlength);
self
}
pub fn with_placeholder(mut self, placeholder: &str) -> Self {
self.placeholder = util::optional_str(placeholder);
self.alter_placeholder(placeholder);
self
}
pub fn autofocus(mut self, toggle: bool) -> Self {
self.autofocus = match toggle {
true => Some("autofocus".to_owned()),
false => None
};
pub fn with_autofocus(mut self, toggle: bool) -> Self {
self.alter_autofocus(toggle);
self
}
pub fn autocomplete(mut self, toggle: bool) -> Self {
self.autocomplete = match toggle {
true => None,
false => Some("off".to_owned())
};
pub fn with_autocomplete(mut self, toggle: bool) -> Self {
self.alter_autocomplete(toggle);
self
}
pub fn disabled(mut self, toggle: bool) -> Self {
self.disabled = match toggle {
true => Some("disabled".to_owned()),
false => None
};
pub fn with_disabled(mut self, toggle: bool) -> Self {
self.alter_disabled(toggle);
self
}
pub fn readonly(mut self, toggle: bool) -> Self {
self.readonly = match toggle {
true => Some("readonly".to_owned()),
false => None
};
pub fn with_readonly(mut self, toggle: bool) -> Self {
self.alter_readonly(toggle);
self
}
pub fn required(mut self, toggle: bool) -> Self {
self.required = match toggle {
true => Some("required".to_owned()),
false => None
};
pub fn with_required(mut self, toggle: bool) -> Self {
self.alter_required(toggle);
self
}
pub fn with_help_text(mut self, help_text: &str) -> Self {
self.help_text = util::optional_str(help_text);
self.alter_help_text(help_text);
self
}
pub fn with_classes(mut self, classes: &str, op: ClassesOp) -> Self {
self.alter_classes(classes, op);
self
}
pub fn using_template(mut self, template: &str) -> Self {
self.alter_template(template);
self
}
// Input ALTER.
pub fn alter_renderable(&mut self, renderable: fn() -> bool) -> &mut Self {
self.renderable = renderable;
self
}
pub fn alter_weight(&mut self, weight: isize) -> &mut Self {
self.weight = weight;
self
}
pub fn alter_name(&mut self, name: &str) -> &mut Self {
self.name.with_value(name);
self.alter_classes(
concat_string!("form-item form-item-", name).as_str(),
ClassesOp::SetDefault
);
self
}
pub fn alter_value(&mut self, value: &str) -> &mut Self {
self.value.with_value(value);
self
}
pub fn alter_label(&mut self, label: &str) -> &mut Self {
self.label.with_value(label);
self
}
pub fn alter_size(&mut self, size: Option<u16>) -> &mut Self {
self.size = size;
self
}
pub fn alter_minlength(&mut self, minlength: Option<u16>) -> &mut Self {
self.minlength = minlength;
self
}
pub fn alter_maxlength(&mut self, maxlength: Option<u16>) -> &mut Self {
self.maxlength = maxlength;
self
}
pub fn alter_placeholder(&mut self, placeholder: &str) -> &mut Self {
self.placeholder.with_value(placeholder);
self
}
pub fn alter_autofocus(&mut self, toggle: bool) -> &mut Self {
self.autofocus.with_value(match toggle {
true => "autofocus",
false => "",
});
self
}
pub fn alter_autocomplete(&mut self, toggle: bool) -> &mut Self {
self.autocomplete.with_value(match toggle {
true => "",
false => "off",
});
self
}
pub fn alter_disabled(&mut self, toggle: bool) -> &mut Self {
self.disabled.with_value(match toggle {
true => "disabled",
false => "",
});
self
}
pub fn alter_readonly(&mut self, toggle: bool) -> &mut Self {
self.readonly.with_value(match toggle {
true => "readonly",
false => "",
});
self
}
pub fn alter_required(&mut self, toggle: bool) -> &mut Self {
self.required.with_value(match toggle {
true => "required",
false => "",
});
self
}
pub fn alter_help_text(&mut self, help_text: &str) -> &mut Self {
self.help_text.with_value(help_text);
self
}
pub fn alter_classes(&mut self, classes: &str, op: ClassesOp) -> &mut Self {
self.classes.alter(classes, op);
self
}
pub fn alter_template(&mut self, template: &str) -> &mut Self {
self.template = template.to_owned();
self
}
// Input GETTERS.
pub fn name(&self) -> &str {
util::assigned_str(&self.name)
pub fn input_type(&self) -> &InputType {
&self.input_type
}
pub fn value(&self) -> &str {
util::assigned_str(&self.value)
pub fn name(&self) -> &Option<String> {
self.name.option()
}
pub fn label(&self) -> &str {
util::assigned_str(&self.label)
pub fn value(&self) -> &Option<String> {
self.value.option()
}
pub fn label(&self) -> &Option<String> {
self.label.option()
}
pub fn size(&self) -> Option<u16> {
@ -272,54 +384,39 @@ impl Input {
self.maxlength
}
pub fn placeholder(&self) -> &str {
util::assigned_str(&self.placeholder)
pub fn placeholder(&self) -> &Option<String> {
self.placeholder.option()
}
pub fn has_autofocus(&self) -> bool {
match &self.autofocus {
Some(_) => true,
_ => false
}
pub fn autofocus(&self) -> &Option<String> {
self.autofocus.option()
}
pub fn has_autocomplete(&self) -> bool {
match &self.autocomplete {
Some(_) => false,
_ => true
}
pub fn autocomplete(&self) -> &Option<String> {
self.autocomplete.option()
}
pub fn is_disabled(&self) -> bool {
match &self.disabled {
Some(_) => true,
_ => false
}
pub fn disabled(&self) -> &Option<String> {
self.disabled.option()
}
pub fn is_readonly(&self) -> bool {
match &self.readonly {
Some(_) => true,
_ => false
}
pub fn readonly(&self) -> &Option<String> {
self.readonly.option()
}
pub fn is_required(&self) -> bool {
match &self.required {
Some(_) => true,
_ => false
}
pub fn required(&self) -> &Option<String> {
self.required.option()
}
pub fn help_text(&self) -> &str {
util::assigned_str(&self.help_text)
pub fn help_text(&self) -> &Option<String> {
self.help_text.option()
}
pub fn classes(&self) -> &Option<String> {
self.classes.option()
}
pub fn template(&self) -> &str {
self.template.as_str()
}
}
fn always() -> bool {
true
}

View file

@ -1,11 +0,0 @@
mod form;
pub use form::{Form, FormMethod};
mod input;
pub use input::Input;
mod hidden;
pub use hidden::Hidden;
mod date;
pub use date::Date;
mod button;
pub use button::Button;

View file

@ -0,0 +1,8 @@
mod row;
pub use row::{
ROW_COMPONENT, Row
};
mod column;
pub use column::{
COLUMN_COMPONENT, Column
};

View file

@ -0,0 +1,135 @@
use crate::prelude::*;
pub const COLUMN_COMPONENT: &str = "pagetop::component::grid::column";
pub struct Column {
renderable: fn() -> bool,
weight : isize,
components: ComponentsBundle,
id : OptIden,
classes : Classes,
template : String,
}
impl ComponentTrait for Column {
fn new() -> Self {
Column {
renderable: render_always,
weight : 0,
components: ComponentsBundle::new(),
id : OptIden::new(),
classes : Classes::new_with_default("col"),
template : "default".to_owned(),
}
}
fn handler(&self) -> &'static str {
COLUMN_COMPONENT
}
fn is_renderable(&self) -> bool {
(self.renderable)()
}
fn weight(&self) -> isize {
self.weight
}
fn default_render(&self, context: &mut InContext) -> Markup {
html! {
div id=[self.id()] class=[self.classes()] {
(self.components().render(context))
}
}
}
fn as_ref_any(&self) -> &dyn AnyComponent {
self
}
fn as_mut_any(&mut self) -> &mut dyn AnyComponent {
self
}
}
impl Column {
// Column CONTAINER.
pub fn add(mut self, component: impl ComponentTrait) -> Self {
self.components.add(component);
self
}
pub fn components(&self) -> &ComponentsBundle {
&self.components
}
// Column BUILDER.
pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self {
self.alter_renderable(renderable);
self
}
pub fn with_weight(mut self, weight: isize) -> Self {
self.alter_weight(weight);
self
}
pub fn with_id(mut self, id: &str) -> Self {
self.alter_id(id);
self
}
pub fn with_classes(mut self, classes: &str, op: ClassesOp) -> Self {
self.alter_classes(classes, op);
self
}
pub fn using_template(mut self, template: &str) -> Self {
self.alter_template(template);
self
}
// Column ALTER.
pub fn alter_renderable(&mut self, renderable: fn() -> bool) -> &mut Self {
self.renderable = renderable;
self
}
pub fn alter_weight(&mut self, weight: isize) -> &mut Self {
self.weight = weight;
self
}
pub fn alter_id(&mut self, id: &str) -> &mut Self {
self.id.with_value(id);
self
}
pub fn alter_classes(&mut self, classes: &str, op: ClassesOp) -> &mut Self {
self.classes.alter(classes, op);
self
}
pub fn alter_template(&mut self, template: &str) -> &mut Self {
self.template = template.to_owned();
self
}
// Column GETTERS.
pub fn id(&self) -> &Option<String> {
self.id.option()
}
pub fn classes(&self) -> &Option<String> {
self.classes.option()
}
pub fn template(&self) -> &str {
self.template.as_str()
}
}

View file

@ -0,0 +1,135 @@
use crate::prelude::*;
pub const ROW_COMPONENT: &str = "pagetop::component::grid::row";
pub struct Row {
renderable: fn() -> bool,
weight : isize,
columns : ComponentsBundle,
id : OptIden,
classes : Classes,
template : String,
}
impl ComponentTrait for Row {
fn new() -> Self {
Row {
renderable: render_always,
weight : 0,
columns : ComponentsBundle::new(),
id : OptIden::new(),
classes : Classes::new_with_default("row"),
template : "default".to_owned(),
}
}
fn handler(&self) -> &'static str {
ROW_COMPONENT
}
fn is_renderable(&self) -> bool {
(self.renderable)()
}
fn weight(&self) -> isize {
self.weight
}
fn default_render(&self, context: &mut InContext) -> Markup {
html! {
div id=[self.id()] class=[self.classes()] {
(self.columns().render(context))
}
}
}
fn as_ref_any(&self) -> &dyn AnyComponent {
self
}
fn as_mut_any(&mut self) -> &mut dyn AnyComponent {
self
}
}
impl Row {
// Row CONTAINER.
pub fn add_column(mut self, column: grid::Column) -> Self {
self.columns.add(column);
self
}
pub fn columns(&self) -> &ComponentsBundle {
&self.columns
}
// Row BUILDER.
pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self {
self.alter_renderable(renderable);
self
}
pub fn with_weight(mut self, weight: isize) -> Self {
self.alter_weight(weight);
self
}
pub fn with_id(mut self, id: &str) -> Self {
self.alter_id(id);
self
}
pub fn with_classes(mut self, classes: &str, op: ClassesOp) -> Self {
self.alter_classes(classes, op);
self
}
pub fn using_template(mut self, template: &str) -> Self {
self.alter_template(template);
self
}
// Row ALTER.
pub fn alter_renderable(&mut self, renderable: fn() -> bool) -> &mut Self {
self.renderable = renderable;
self
}
pub fn alter_weight(&mut self, weight: isize) -> &mut Self {
self.weight = weight;
self
}
pub fn alter_id(&mut self, id: &str) -> &mut Self {
self.id.with_value(id);
self
}
pub fn alter_classes(&mut self, classes: &str, op: ClassesOp) -> &mut Self {
self.classes.alter(classes, op);
self
}
pub fn alter_template(&mut self, template: &str) -> &mut Self {
self.template = template.to_owned();
self
}
// Row GETTERS.
pub fn id(&self) -> &Option<String> {
self.id.option()
}
pub fn classes(&self) -> &Option<String> {
self.classes.option()
}
pub fn template(&self) -> &str {
self.template.as_str()
}
}

View file

@ -0,0 +1,215 @@
use crate::prelude::*;
pub const HEADING_COMPONENT: &str = "pagetop::component::heading";
pub enum HeadingType { H1, H2, H3, H4, H5, H6 }
pub enum HeadingDisplay {
XxLarge,
Large,
Medium,
Small,
XxSmall,
Normal,
}
pub struct Heading {
renderable: fn() -> bool,
weight : isize,
heading : HeadingType,
html : Markup,
display : HeadingDisplay,
id : OptIden,
classes : Classes,
template : String,
}
impl ComponentTrait for Heading {
fn new() -> Self {
Heading {
renderable: render_always,
weight : 0,
heading : HeadingType::H1,
html : html! {},
display : HeadingDisplay::Normal,
id : OptIden::new(),
classes : Classes::new(),
template : "default".to_owned(),
}
}
fn handler(&self) -> &'static str {
HEADING_COMPONENT
}
fn is_renderable(&self) -> bool {
(self.renderable)()
}
fn weight(&self) -> isize {
self.weight
}
fn default_render(&self, _: &mut InContext) -> Markup {
html! { @match &self.heading() {
HeadingType::H1 => h1 id=[self.id()] class=[self.classes()] { (*self.html()) },
HeadingType::H2 => h2 id=[self.id()] class=[self.classes()] { (*self.html()) },
HeadingType::H3 => h3 id=[self.id()] class=[self.classes()] { (*self.html()) },
HeadingType::H4 => h4 id=[self.id()] class=[self.classes()] { (*self.html()) },
HeadingType::H5 => h5 id=[self.id()] class=[self.classes()] { (*self.html()) },
HeadingType::H6 => h6 id=[self.id()] class=[self.classes()] { (*self.html()) },
}}
}
fn as_ref_any(&self) -> &dyn AnyComponent {
self
}
fn as_mut_any(&mut self) -> &mut dyn AnyComponent {
self
}
}
impl Heading {
pub fn h1(html: Markup) -> Self {
Heading::new().with_heading(HeadingType::H1).with_html(html)
}
pub fn h2(html: Markup) -> Self {
Heading::new().with_heading(HeadingType::H2).with_html(html)
}
pub fn h3(html: Markup) -> Self {
Heading::new().with_heading(HeadingType::H3).with_html(html)
}
pub fn h4(html: Markup) -> Self {
Heading::new().with_heading(HeadingType::H4).with_html(html)
}
pub fn h5(html: Markup) -> Self {
Heading::new().with_heading(HeadingType::H5).with_html(html)
}
pub fn h6(html: Markup) -> Self {
Heading::new().with_heading(HeadingType::H6).with_html(html)
}
// Heading BUILDER.
pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self {
self.alter_renderable(renderable);
self
}
pub fn with_weight(mut self, weight: isize) -> Self {
self.alter_weight(weight);
self
}
pub fn with_heading(mut self, heading: HeadingType) -> Self {
self.alter_heading(heading);
self
}
pub fn with_html(mut self, html: Markup) -> Self {
self.alter_html(html);
self
}
pub fn with_display(mut self, display: HeadingDisplay) -> Self {
self.alter_display(display);
self
}
pub fn with_id(mut self, id: &str) -> Self {
self.alter_id(id);
self
}
pub fn with_classes(mut self, classes: &str, op: ClassesOp) -> Self {
self.alter_classes(classes, op);
self
}
pub fn using_template(mut self, template: &str) -> Self {
self.alter_template(template);
self
}
// Heading ALTER.
pub fn alter_renderable(&mut self, renderable: fn() -> bool) -> &mut Self {
self.renderable = renderable;
self
}
pub fn alter_weight(&mut self, weight: isize) -> &mut Self {
self.weight = weight;
self
}
pub fn alter_heading(&mut self, heading: HeadingType) -> &mut Self {
self.heading = heading;
self
}
pub fn alter_html(&mut self, html: Markup) -> &mut Self {
self.html = html;
self
}
pub fn alter_display(&mut self, display: HeadingDisplay) -> &mut Self {
self.display = display;
self.classes.alter(match &self.display() {
HeadingDisplay::XxLarge => "display-2",
HeadingDisplay::Large => "display-3",
HeadingDisplay::Medium => "display-4",
HeadingDisplay::Small => "display-5",
HeadingDisplay::XxSmall => "display-6",
HeadingDisplay::Normal => "",
}, ClassesOp::SetDefault);
self
}
pub fn alter_id(&mut self, id: &str) -> &mut Self {
self.id.with_value(id);
self
}
pub fn alter_classes(&mut self, classes: &str, op: ClassesOp) -> &mut Self {
self.classes.alter(classes, op);
self
}
pub fn alter_template(&mut self, template: &str) -> &mut Self {
self.template = template.to_owned();
self
}
// Paragraph GETTERS.
pub fn heading(&self) -> &HeadingType {
&self.heading
}
pub fn html(&self) -> &Markup {
&self.html
}
pub fn display(&self) -> &HeadingDisplay {
&self.display
}
pub fn id(&self) -> &Option<String> {
self.id.option()
}
pub fn classes(&self) -> &Option<String> {
self.classes.option()
}
pub fn template(&self) -> &str {
self.template.as_str()
}
}

View file

@ -0,0 +1,95 @@
use crate::prelude::*;
pub const ICON_COMPONENT: &str = "pagetop::component::icon";
pub struct Icon {
renderable: fn() -> bool,
weight : isize,
icon_name : String,
}
impl ComponentTrait for Icon {
fn new() -> Self {
Icon {
renderable: render_always,
weight : 0,
icon_name : "question-circle-fill".to_owned(),
}
}
fn handler(&self) -> &'static str {
ICON_COMPONENT
}
fn is_renderable(&self) -> bool {
(self.renderable)()
}
fn weight(&self) -> isize {
self.weight
}
fn default_render(&self, context: &mut InContext) -> Markup {
context
.add_stylesheet(StyleSheet::with_source(
"/theme/icons/bootstrap-icons.css?ver=1.8.2"
));
let name = concat_string!("bi-", self.icon_name);
html! { i class=(name) {}; }
}
fn as_ref_any(&self) -> &dyn AnyComponent {
self
}
fn as_mut_any(&mut self) -> &mut dyn AnyComponent {
self
}
}
impl Icon {
pub fn with(icon_name: &str) -> Self {
Icon::new().with_icon_name(icon_name)
}
// Icon BUILDER.
pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self {
self.alter_renderable(renderable);
self
}
pub fn with_weight(mut self, weight: isize) -> Self {
self.alter_weight(weight);
self
}
pub fn with_icon_name(mut self, name: &str) -> Self {
self.alter_icon_name(name);
self
}
// Icon ALTER.
pub fn alter_renderable(&mut self, renderable: fn() -> bool) -> &mut Self {
self.renderable = renderable;
self
}
pub fn alter_weight(&mut self, weight: isize) -> &mut Self {
self.weight = weight;
self
}
pub fn alter_icon_name(&mut self, name: &str) -> &mut Self {
self.icon_name = name.to_owned();
self
}
// Icon GETTERS.
pub fn icon_name(&self) -> &str {
&self.icon_name
}
}

View file

@ -0,0 +1,142 @@
use crate::prelude::*;
pub const IMAGE_COMPONENT: &str = "pagetop::component::image";
pub struct Image {
renderable: fn() -> bool,
weight : isize,
source : OptAttr,
id : OptIden,
classes : Classes,
template : String,
}
impl ComponentTrait for Image {
fn new() -> Self {
Image {
renderable: render_always,
weight : 0,
source : OptAttr::new(),
id : OptIden::new(),
classes : Classes::new_with_default("img-fluid"),
template : "default".to_owned(),
}
}
fn handler(&self) -> &'static str {
IMAGE_COMPONENT
}
fn is_renderable(&self) -> bool {
(self.renderable)()
}
fn weight(&self) -> isize {
self.weight
}
fn default_render(&self, _: &mut InContext) -> Markup {
html! {
img
src=[self.source()]
id=[self.id()]
class=[self.classes()];
}
}
fn as_ref_any(&self) -> &dyn AnyComponent {
self
}
fn as_mut_any(&mut self) -> &mut dyn AnyComponent {
self
}
}
impl Image {
pub fn image(source: &str) -> Self {
Image::new().with_source(source)
}
// Image BUILDER.
pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self {
self.alter_renderable(renderable);
self
}
pub fn with_weight(mut self, weight: isize) -> Self {
self.alter_weight(weight);
self
}
pub fn with_source(mut self, source: &str) -> Self {
self.alter_source(source);
self
}
pub fn with_id(mut self, id: &str) -> Self {
self.alter_id(id);
self
}
pub fn with_classes(mut self, classes: &str, op: ClassesOp) -> Self {
self.alter_classes(classes, op);
self
}
pub fn using_template(mut self, template: &str) -> Self {
self.alter_template(template);
self
}
// Image ALTER.
pub fn alter_renderable(&mut self, renderable: fn() -> bool) -> &mut Self {
self.renderable = renderable;
self
}
pub fn alter_weight(&mut self, weight: isize) -> &mut Self {
self.weight = weight;
self
}
pub fn alter_source(&mut self, source: &str) -> &mut Self {
self.source.with_value(source);
self
}
pub fn alter_id(&mut self, id: &str) -> &mut Self {
self.id.with_value(id);
self
}
pub fn alter_classes(&mut self, classes: &str, op: ClassesOp) -> &mut Self {
self.classes.alter(classes, op);
self
}
pub fn alter_template(&mut self, template: &str) -> &mut Self {
self.template = template.to_owned();
self
}
// Image GETTERS.
pub fn source(&self) -> &Option<String> {
self.source.option()
}
pub fn id(&self) -> &Option<String> {
self.id.option()
}
pub fn classes(&self) -> &Option<String> {
self.classes.option()
}
pub fn template(&self) -> &str {
self.template.as_str()
}
}

View file

@ -1,195 +1,228 @@
use crate::prelude::*;
enum MenuItemType {
pub const MENU_COMPONENT: &str = "pagetop::component::menu";
pub const MENUITEM_COMPONENT: &str = "pagetop::component::menu_item";
pub enum MenuItemType {
Label(String),
Link(String, String),
LinkBlank(String, String),
Markup(Markup),
Html(Markup),
Separator,
Submenu(String, Menu),
Void,
}
// -----------------------------------------------------------------------------
// MenuItem.
// -----------------------------------------------------------------------------
pub struct MenuItem {
renderable: fn() -> bool,
weight : i8,
item_type : Option<MenuItemType>,
weight : isize,
item_type : MenuItemType,
}
impl PageComponent for MenuItem {
fn prepare() -> Self {
impl ComponentTrait for MenuItem {
fn new() -> Self {
MenuItem {
renderable: always,
renderable: render_always,
weight : 0,
item_type : None,
item_type : MenuItemType::Void,
}
}
fn handler(&self) -> &'static str {
MENUITEM_COMPONENT
}
fn is_renderable(&self) -> bool {
(self.renderable)()
}
fn weight(&self) -> i8 {
fn weight(&self) -> isize {
self.weight
}
fn default_render(&self, assets: &mut PageAssets) -> Markup {
match &self.item_type {
Some(MenuItemType::Label(label)) => html! {
fn default_render(&self, context: &mut InContext) -> Markup {
match self.item_type() {
MenuItemType::Label(label) => html! {
li class="label" { a href="#" { (label) } }
},
Some(MenuItemType::Link(label, path)) => html! {
MenuItemType::Link(label, path) => html! {
li class="link" { a href=(path) { (label) } }
},
Some(MenuItemType::LinkBlank(label, path)) => html! {
MenuItemType::LinkBlank(label, path) => html! {
li class="link_blank" {
a href=(path) target="_blank" { (label) }
}
},
Some(MenuItemType::Markup(markup)) => html! {
li class="markup" { (*markup) }
MenuItemType::Html(html) => html! {
li class="html" { (*html) }
},
Some(MenuItemType::Submenu(label, menu)) => html! {
MenuItemType::Submenu(label, menu) => html! {
li class="submenu" {
a href="#" { (label) }
ul {
(menu.render_items(assets))
(menu.items().render(context))
}
}
},
Some(MenuItemType::Separator) => html! {
MenuItemType::Separator => html! {
li class="separator" { }
},
None => html! {}
MenuItemType::Void => html! {},
}
}
fn as_ref_any(&self) -> &dyn AnyComponent {
self
}
fn as_mut_any(&mut self) -> &mut dyn AnyComponent {
self
}
}
impl MenuItem {
pub fn label(label: &str) -> Self {
MenuItem {
renderable: always,
renderable: render_always,
weight : 0,
item_type : Some(MenuItemType::Label(label.to_owned())),
item_type : MenuItemType::Label(label.to_owned()),
}
}
pub fn link(label: &str, path: &str) -> Self {
MenuItem {
renderable: always,
renderable: render_always,
weight : 0,
item_type : Some(MenuItemType::Link(
item_type : MenuItemType::Link(
label.to_owned(),
path.to_owned(),
)),
),
}
}
pub fn link_blank(label: &str, path: &str) -> Self {
MenuItem {
renderable: always,
renderable: render_always,
weight : 0,
item_type : Some(MenuItemType::LinkBlank(
item_type : MenuItemType::LinkBlank(
label.to_owned(),
path.to_owned(),
)),
),
}
}
pub fn markup(markup: Markup) -> Self {
pub fn html(html: Markup) -> Self {
MenuItem {
renderable: always,
renderable: render_always,
weight : 0,
item_type : Some(MenuItemType::Markup(markup)),
item_type : MenuItemType::Html(html),
}
}
pub fn separator() -> Self {
MenuItem {
renderable: always,
renderable: render_always,
weight : 0,
item_type : Some(MenuItemType::Separator),
item_type : MenuItemType::Separator,
}
}
pub fn submenu(label: &str, menu: Menu) -> Self {
MenuItem {
renderable: always,
renderable: render_always,
weight : 0,
item_type : Some(MenuItemType::Submenu(
item_type : MenuItemType::Submenu(
label.to_owned(),
menu
)),
),
}
}
// MenuItem BUILDER.
pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self {
self.alter_renderable(renderable);
self
}
pub fn with_weight(mut self, weight: isize) -> Self {
self.alter_weight(weight);
self
}
// MenuItem ALTER.
pub fn alter_renderable(&mut self, renderable: fn() -> bool) -> &mut Self {
self.renderable = renderable;
self
}
pub fn with_weight(mut self, weight: i8) -> Self {
pub fn alter_weight(&mut self, weight: isize) -> &mut Self {
self.weight = weight;
self
}
// MenuItem GETTERS.
pub fn item_type(&self) -> &MenuItemType {
&self.item_type
}
}
// -----------------------------------------------------------------------------
// Menu.
// -----------------------------------------------------------------------------
pub struct Menu {
renderable: fn() -> bool,
weight : i8,
id : Option<String>,
items : PageContainer,
weight : isize,
items : ComponentsBundle,
id : OptIden,
classes : Classes,
template : String,
}
impl PageComponent for Menu {
fn prepare() -> Self {
impl ComponentTrait for Menu {
fn new() -> Self {
Menu {
renderable: always,
renderable: render_always,
weight : 0,
id : None,
items : PageContainer::new(),
items : ComponentsBundle::new(),
id : OptIden::new(),
classes : Classes::new_with_default("sm sm-clean"),
template : "default".to_owned(),
}
}
fn handler(&self) -> &'static str {
MENU_COMPONENT
}
fn is_renderable(&self) -> bool {
(self.renderable)()
}
fn weight(&self) -> i8 {
fn weight(&self) -> isize {
self.weight
}
fn default_render(&self, assets: &mut PageAssets) -> Markup {
assets
.add_stylesheet(StyleSheet::source(
fn default_render(&self, context: &mut InContext) -> Markup {
context
.add_stylesheet(StyleSheet::with_source(
"/theme/menu/css/menu.css?ver=1.1.1"
))
.add_stylesheet(StyleSheet::source(
.add_stylesheet(StyleSheet::with_source(
"/theme/menu/css/menu-clean.css?ver=1.1.1"
))
.add_javascript(JavaScript::source(
.add_javascript(JavaScript::with_source(
"/theme/menu/js/menu.min.js?ver=1.1.1"
))
.add_jquery();
let id = assets.serial_id(self.name(), self.id());
let id = context.required_id::<Menu>(self.id());
html! {
ul id=(id) class="sm sm-clean" {
(self.render_items(assets))
ul id=(id) class=[self.classes()] {
(self.items().render(context))
}
script type="text/javascript" defer {
"jQuery(function(){jQuery('#" (id) "').smartmenus({"
@ -199,54 +232,94 @@ impl PageComponent for Menu {
}
}
}
fn as_ref_any(&self) -> &dyn AnyComponent {
self
}
fn as_mut_any(&mut self) -> &mut dyn AnyComponent {
self
}
}
impl Menu {
// Menu BUILDER.
pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self {
self.renderable = renderable;
self
}
pub fn with_weight(mut self, weight: i8) -> Self {
self.weight = weight;
self
}
pub fn with_id(mut self, id: &str) -> Self {
self.id = util::valid_id(id);
self
}
// Menu CONTAINER.
pub fn add(mut self, item: MenuItem) -> Self {
self.items.add(item);
self
}
pub fn items(&self) -> &ComponentsBundle {
&self.items
}
// Menu BUILDER.
pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self {
self.alter_renderable(renderable);
self
}
pub fn with_weight(mut self, weight: isize) -> Self {
self.alter_weight(weight);
self
}
pub fn with_id(mut self, id: &str) -> Self {
self.alter_id(id);
self
}
pub fn with_classes(mut self, classes: &str, op: ClassesOp) -> Self {
self.alter_classes(classes, op);
self
}
pub fn using_template(mut self, template: &str) -> Self {
self.alter_template(template);
self
}
// Menu ALTER.
pub fn alter_renderable(&mut self, renderable: fn() -> bool) -> &mut Self {
self.renderable = renderable;
self
}
pub fn alter_weight(&mut self, weight: isize) -> &mut Self {
self.weight = weight;
self
}
pub fn alter_id(&mut self, id: &str) -> &mut Self {
self.id.with_value(id);
self
}
pub fn alter_classes(&mut self, classes: &str, op: ClassesOp) -> &mut Self {
self.classes.alter(classes, op);
self
}
pub fn alter_template(&mut self, template: &str) -> &mut Self {
self.template = template.to_owned();
self
}
// Menu GETTERS.
pub fn id(&self) -> &str {
util::assigned_str(&self.id)
pub fn id(&self) -> &Option<String> {
self.id.option()
}
pub fn classes(&self) -> &Option<String> {
self.classes.option()
}
pub fn template(&self) -> &str {
self.template.as_str()
}
// Menu EXTRAS.
pub fn render_items(&self, assets: &mut PageAssets) -> Markup {
html! { (self.items.render(assets)) }
}
}
fn always() -> bool {
true
}

View file

@ -1,11 +0,0 @@
mod container;
pub use container::Container;
mod chunck;
pub use chunck::Chunck;
mod block;
pub use block::Block;
mod menu;
pub use menu::{Menu, MenuItem};
pub mod form;
pub use form::{Form, FormMethod};

View file

@ -0,0 +1,172 @@
use crate::prelude::*;
pub const PARAGRAPH_COMPONENT: &str = "pagetop::component::paragraph";
pub enum ParagraphDisplay {
XxLarge,
Large,
Medium,
Small,
XxSmall,
Normal,
}
pub struct Paragraph {
renderable: fn() -> bool,
weight : isize,
html : Markup,
display : ParagraphDisplay,
id : OptIden,
classes : Classes,
template : String,
}
impl ComponentTrait for Paragraph {
fn new() -> Self {
Paragraph {
renderable: render_always,
weight : 0,
html : html! {},
display : ParagraphDisplay::Normal,
id : OptIden::new(),
classes : Classes::new(),
template : "default".to_owned(),
}
}
fn handler(&self) -> &'static str {
PARAGRAPH_COMPONENT
}
fn is_renderable(&self) -> bool {
(self.renderable)()
}
fn weight(&self) -> isize {
self.weight
}
fn default_render(&self, _: &mut InContext) -> Markup {
html! {
p id=[self.id()] class=[self.classes()] { (*self.html()) }
}
}
fn as_ref_any(&self) -> &dyn AnyComponent {
self
}
fn as_mut_any(&mut self) -> &mut dyn AnyComponent {
self
}
}
impl Paragraph {
pub fn with(html: Markup) -> Self {
Paragraph::new().with_html(html)
}
// Paragraph BUILDER.
pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self {
self.alter_renderable(renderable);
self
}
pub fn with_weight(mut self, weight: isize) -> Self {
self.alter_weight(weight);
self
}
pub fn with_html(mut self, html: Markup) -> Self {
self.alter_html(html);
self
}
pub fn with_display(mut self, display: ParagraphDisplay) -> Self {
self.alter_display(display);
self
}
pub fn with_id(mut self, id: &str) -> Self {
self.alter_id(id);
self
}
pub fn with_classes(mut self, classes: &str, op: ClassesOp) -> Self {
self.alter_classes(classes, op);
self
}
pub fn using_template(mut self, template: &str) -> Self {
self.alter_template(template);
self
}
// Paragraph ALTER.
pub fn alter_renderable(&mut self, renderable: fn() -> bool) -> &mut Self {
self.renderable = renderable;
self
}
pub fn alter_weight(&mut self, weight: isize) -> &mut Self {
self.weight = weight;
self
}
pub fn alter_html(&mut self, html: Markup) -> &mut Self {
self.html = html;
self
}
pub fn alter_display(&mut self, display: ParagraphDisplay) -> &mut Self {
self.display = display;
self.classes.alter(match &self.display() {
ParagraphDisplay::XxLarge => "fs-2",
ParagraphDisplay::Large => "fs-3",
ParagraphDisplay::Medium => "fs-4",
ParagraphDisplay::Small => "fs-5",
ParagraphDisplay::XxSmall => "fs-6",
ParagraphDisplay::Normal => "",
}, ClassesOp::SetDefault);
self
}
pub fn alter_id(&mut self, id: &str) -> &mut Self {
self.id.with_value(id);
self
}
pub fn alter_classes(&mut self, classes: &str, op: ClassesOp) -> &mut Self {
self.classes.alter(classes, op);
self
}
pub fn alter_template(&mut self, template: &str) -> &mut Self {
self.template = template.to_owned();
self
}
// Paragraph GETTERS.
pub fn html(&self) -> &Markup {
&self.html
}
pub fn display(&self) -> &ParagraphDisplay {
&self.display
}
pub fn id(&self) -> &Option<String> {
self.id.option()
}
pub fn classes(&self) -> &Option<String> {
self.classes.option()
}
pub fn template(&self) -> &str {
self.template.as_str()
}
}

View file

@ -0,0 +1 @@
pub mod demopage;

View file

@ -1,2 +0,0 @@
module_fullname = Admin module
module_description = Administration module.

View file

@ -1,2 +0,0 @@
module_fullname = Admin module
module_description = Módulo de administración.

View file

@ -1,28 +0,0 @@
use crate::prelude::*;
localize!("en-US", "src/base/module/admin/locales");
mod summary;
pub struct AdminModule;
impl Module for AdminModule {
fn name(&self) -> &'static str {
"admin"
}
fn fullname(&self) -> String {
l("module_fullname")
}
fn description(&self) -> Option<String> {
Some(l("module_description"))
}
fn configure_module(&self, cfg: &mut server::web::ServiceConfig) {
cfg.service(
server::web::scope("/admin")
.route("", server::web::get().to(summary::summary))
);
}
}

View file

@ -1,58 +0,0 @@
use crate::prelude::*;
use super::l;
pub async fn summary() -> server::Result<Markup> {
let top_menu = Menu::prepare()
.add(MenuItem::label(l("module_fullname").as_str()))
.add(MenuItem::link("Opción 2", "https://www.google.es"))
.add(MenuItem::link_blank("Opción 3", "https://www.google.es"))
.add(MenuItem::submenu("Submenú 1", Menu::prepare()
.add(MenuItem::label("Opción 1"))
.add(MenuItem::label("Opción 2"))
))
.add(MenuItem::separator())
.add(MenuItem::submenu("Submenú 2", Menu::prepare()
.add(MenuItem::label("Opción 1"))
.add(MenuItem::label("Opción 2"))
))
.add(MenuItem::label("Opción 4"));
let side_menu = Menu::prepare()
.add(MenuItem::label("Opción 1"))
.add(MenuItem::link("Opción 2", "https://www.google.es"))
.add(MenuItem::link_blank("Opción 3", "https://www.google.es"))
.add(MenuItem::submenu("Submenú 1", Menu::prepare()
.add(MenuItem::label("Opción 1"))
.add(MenuItem::label("Opción 2"))
))
.add(MenuItem::separator())
.add(MenuItem::submenu("Submenú 2", Menu::prepare()
.add(MenuItem::label("Opción 1"))
.add(MenuItem::label("Opción 2"))
))
.add(MenuItem::label("Opción 4"));
Page::prepare()
.using_theme("bootsier")
.with_title("Admin")
.add_to("top-menu", top_menu)
.add_to("content", Container::row()
.add(Container::column()
.add(side_menu)
)
.add(Container::column()
.add(Chunck::markup(html! {
p { "Columna 2"}
}))
)
)
.using_template("admin")
.render()
}

View file

@ -0,0 +1,230 @@
use crate::prelude::*;
pub const DEMOPAGE_MODULE: &str = "pagetop::module::demopage";
localize!("src/base/module/demopage/locales");
pub struct Demopage;
impl ModuleTrait for Demopage {
fn handler(&self) -> &'static str {
DEMOPAGE_MODULE
}
fn name(&self) -> String {
l("module_name")
}
fn description(&self) -> Option<String> {
Some(l("module_description"))
}
fn configure_service(&self, cfg: &mut app::web::ServiceConfig) {
cfg.route("/", app::web::get().to(demo));
}
}
async fn demo() -> app::Result<Markup> {
Page::new()
.with_title(l("page_title").as_str())
.add_to("content", hello_world())
.add_to("content", hello_world_original())
.add_to("content", just_visiting())
.add_to("content", about_pagetop())
.add_to("content", promo_pagetop())
.add_to("content", reporting_problems())
.render()
}
fn hello_world() -> Container {
Container::header()
.add(grid::Row::new()
.add_column(grid::Column::new()
.add(Heading::h1(html! {
(l("page_title"))
}).with_display(HeadingDisplay::Large))
.add(Paragraph::with(html! {
(t("welcome_to", &args![
"app" => SETTINGS.app.name.as_str()
]))
}))
.add(Paragraph::with(html! {
(e("welcome_intro", &args![
"app" => format!("<strong>{}</strong>", &SETTINGS.app.name)
]))
}).with_display(ParagraphDisplay::Small))
.add(Paragraph::with(html! {
(e("welcome_pagetop", &args![
"pagetop" => "<a href=\"https://pagetop-rs\">PageTop</a>"
]))
}))
.add(Anchor::button("#",
html! {
("Offered services")
}).with_left_icon(
Icon::with("card-checklist")
)
)
.add(Anchor::button("#",
html! {
("Get quote")
}).with_left_icon(
Icon::with("envelope-open-heart-fill")
)
)
)
.add_column(grid::Column::new()
.add(Image::image("/bootsier/images/demo-header.svg"))
)
)
}
fn hello_world_original() -> Chunck {
Chunck::with(html! {
header id="header" class="header" {
div class="container" {
div class="row" {
div class="col-lg-6 col-xl-5" {
div class="text-container" {
div class="section-title" {
(t("welcome_to", &args![
"app" => SETTINGS.app.name.as_str()
]))
}
h1 class="h1-large" {
(l("page_title"))
}
p class="p-large" {
(e("welcome_intro", &args![
"app" => format!(
"<strong>{}</strong>",
&SETTINGS.app.name
)
]))
}
p {
(e("welcome_pagetop", &args![
"pagetop" => "<a href=\"https://pagetop-rs\">PageTop</a>"
]))
}
a class="btn-solid-lg" href="#services" {
"Offered services"
}
a class="quote" href="#contact" {
i class="fas fa-paper-plane" {}
"Get quote"
}
}
}
div class="col-lg-6 col-xl-7" {
div class="image-container" {
img class="img-fluid" src="/bootsier/images/demo-header.svg" alt="alternative" {}
}
}
}
}
}
})
}
fn just_visiting() -> Chunck {
Chunck::with(html! {
div id="details" class="basic-1" {
div class="container" {
div class="row" {
div class="col-lg-6 col-xl-7" {
div class="image-container" {
img class="img-fluid" src="/bootsier/images/demo-visiting.svg" alt="alternative" {}
}
}
div class="col-lg-6 col-xl-5" {
div class="text-container" {
h2 {
span {
(l("visiting_title"))
}
br;
(l("visiting_subtitle"))
}
p { (l("visiting_text1")) }
p { (l("visiting_text2")) }
a class="btn-solid-reg" data-bs-toggle="modal" data-bs-target="#staticBackdrop" { "Modal" }
}
}
}
}
}
})
}
fn about_pagetop() -> Chunck {
Chunck::with(html! {
div id="pagetop" class="basic-2" {
div class="container" {
div class="row" {
div class="col-lg-6 col-xl-5" {
div class="text-container" {
h2 { (l("pagetop_title")) }
p { (l("pagetop_text1")) }
p { (l("pagetop_text2")) }
p { (l("pagetop_text3")) }
}
}
div class="col-lg-6 col-xl-7" {
div class="image-container" {
img class="img-fluid" src="/bootsier/images/demo-pagetop.svg" alt="alternative" {}
}
}
}
}
}
})
}
fn promo_pagetop() -> Chunck {
Chunck::with(html! {
div id="promo" class="basic-3" {
div class="container" {
div class="row" {
div class="col-lg-6 col-xl-5" {
div class="text-container" {
h2 { (l("pagetop_promo_title")) }
p { (e("pagetop_promo_text1", &args![
"pagetop" =>
"<a href=\"https://pagetop-rs\">PageTop</a>"
])) }
}
}
div class="col-lg-6 col-xl-7" {
div class="image-container" {
img class="img-fluid" src="/bootsier/images/demo-pagetop.svg" alt="alternative" {}
}
}
}
}
}
})
}
fn reporting_problems() -> Chunck {
Chunck::with(html! {
div id="reporting" class="basic-4" {
div class="container" {
div class="row" {
div class="col-lg-6 col-xl-5" {
div class="text-container" {
h2 { (l("report_problems_title")) }
p { (l("report_problems_text1")) }
p { (l("report_problems_text2")) }
}
}
div class="col-lg-6 col-xl-7" {
div class="image-container" {
img class="img-fluid" src="/bootsier/images/demo-pagetop.svg" alt="alternative" {}
}
}
}
}
}
})
}

View file

@ -0,0 +1,25 @@
module_name = Default homepage
module_description = Displays a demo homepage when none is configured.
page_title = Hello world!
welcome_to = Welcome to { $app }
welcome_intro = This page is used to test the proper operation of { $app } after installation.
welcome_pagetop = This web solution is powered by { $pagetop }.
visiting_title = Just visiting?
visiting_subtitle = Are you user of this website?
visiting_text1 = If you don't know what this page is about, this probably means that the site is either experiencing problems or is undergoing routine maintenance.
visiting_text2 = If the problem persists, please contact your system administrator.
pagetop_title = About PageTop
pagetop_text1 = If you can read this page, it means that the PageTop server is working properly, but has not yet been configured.
pagetop_text2 = PageTop defines an interface for the most stable and popular Rust packages to build modular, extensible and configurable web solutions.
pagetop_text3 = For information on PageTop please visit the "PageTop website".
pagetop_promo_title = Promoting PageTop
pagetop_promo_text1 = You are free to use the image below on applications powered by { $pagetop }. Thanks for using PageTop!
report_problems_title = Reporting Problems
report_problems_text1 = Please use the GitLab tool to report bugs in PageTop. However, check "existing bug reports" before reporting a new bug.
report_problems_text2 = Please report bugs specific to modules (such as admin, and others) to respective packages, not to PageTop itself.

View file

@ -0,0 +1,25 @@
module_name = Página de inicio predeterminada
module_description = Muestra una página de demostración predeterminada cuando no hay ninguna configurada.
page_title = ¡Hola mundo!
welcome_to = Bienvenido a { $app }
welcome_intro = Esta página se utiliza para probar el correcto funcionamiento de { $app } después de la instalación.
welcome_pagetop = Esta solución web funciona con { $pagetop }.
visiting_title = ¿Sólo de visita?
visiting_subtitle = ¿Eres usuario de este sitio web?
visiting_text1 = Si no sabes de qué trata esta página, probablemente significa que el sitio está experimentando problemas o está pasando por un mantenimiento de rutina.
visiting_text2 = Si el problema persiste, póngase en contacto con el administrador del sistema.
pagetop_title = Sobre PageTop
pagetop_text1 = Si puedes leer esta página, significa que el servidor PageTop funciona correctamente, pero aún no se ha configurado.
pagetop_text2 = PageTop define una interfaz para los paquetes Rust más estables y populares para crear soluciones web modulares, extensibles y configurables.
pagetop_text3 = Para obtener información sobre PageTop, visita el "sitio web de PageTop".
pagetop_promo_title = Promociona PageTop
pagetop_promo_text1 = Puedes usar la siguiente imagen en aplicaciones desarrolladas sobre { $pagetop }. ¡Gracias por usar PageTop!
report_problems_title = Informando Problemas
report_problems_text1 = Utilice la herramienta GitLab para informar errores en PageTop. Sin embargo, verifique los "informes de errores existentes" antes de informar de un nuevo error.
report_problems_text2 = Informe los errores específicos de los módulos (como admin y otros) a los paquetes respectivos, no a PageTop en sí.

View file

@ -1,21 +0,0 @@
module_fullname = Default homepage
module_description = Displays a default homepage when none is configured.
page_title = Hello world!
text_welcome = This page is used to test the proper operation of { $app } after installation. This web solution is powered by { $pagetop }. If you can read this page, it means that the PageTop server is working properly, but has not yet been configured.
title_normal_user = Just visiting?
text1_normal_user = If you are a normal user of this web site and don't know what this page is about, this probably means that the site is either experiencing problems or is undergoing routine maintenance.
text2_normal_user = If the problem persists, please contact your system administrator.
title_about_pagetop = About PageTop
text1_about_pagetop = PageTop defines an interface for the most stable and popular Rust packages to build modular, extensible and configurable web solutions.
text2_about_pagetop = For information on PageTop please visit the "PageTop website".
title_promo_pagetop = Promoting PageTop
text1_promo_pagetop = You are free to use the image below on applications powered by { $pagetop }. Thanks for using PageTop!
title_report_problems = Reporting Problems
text1_report_problems = Please use the GitLab tool to report bugs in PageTop. However, check "existing bug reports" before reporting a new bug.
text2_report_problems = Please report bugs specific to modules (such as admin, and others) to respective packages, not to PageTop itself.

View file

@ -1,21 +0,0 @@
module_fullname = Página de inicio predeterminada
module_description = Muestra una página de inicio predeterminada cuando no hay ninguna configurada.
page_title = ¡Hola mundo!
text_welcome = Esta página se utiliza para probar el correcto funcionamiento de { $app } después de la instalación. Esta solución web funciona con { $pagetop }. Si puede leer esta página, significa que el servidor PageTop funciona correctamente, pero aún no se ha configurado.
title_normal_user = ¿Sólo de visita?
text1_normal_user = Si usted es un usuario normal de este sitio web y no sabe de qué trata esta página, probablemente significa que el sitio está experimentando problemas o está pasando por un mantenimiento de rutina.
text2_normal_user = Si el problema persiste, póngase en contacto con el administrador del sistema.
title_about_pagetop = Sobre PageTop
text1_about_pagetop = PageTop define una interfaz para los paquetes Rust más estables y populares para crear soluciones web modulares, extensibles y configurables.
text2_about_pagetop = Para obtener información sobre PageTop, visite el "sitio web de PageTop".
title_promo_pagetop = Promociona PageTop
text1_promo_pagetop = Puede usar la siguiente imagen en aplicaciones desarrolladas sobre { $pagetop }. ¡Gracias por usar PageTop!
title_report_problems = Informando Problemas
text1_report_problems = Utilice la herramienta GitLab para informar errores en PageTop. Sin embargo, verifique los "informes de errores existentes" antes de informar de un nuevo error.
text2_report_problems = Informe los errores específicos de los módulos (como admin y otros) a los paquetes respectivos, no a PageTop en sí.

View file

@ -1,74 +0,0 @@
use crate::prelude::*;
localize!("en-US", "src/base/module/homepage/locales");
pub struct HomepageModule;
impl Module for HomepageModule {
fn name(&self) -> &'static str {
"homepage"
}
fn fullname(&self) -> String {
l("module_fullname")
}
fn description(&self) -> Option<String> {
Some(l("module_description"))
}
fn configure_module(&self, cfg: &mut server::web::ServiceConfig) {
cfg.route("/", server::web::get().to(home));
}
}
async fn home() -> server::Result<Markup> {
Page::prepare()
.with_title(
l("page_title").as_str()
)
.add_to("content", Container::prepare()
.with_id("welcome")
.add(Chunck::markup(html! {
h1 { (l("page_title")) }
p { (e("text_welcome", &args![
"app" => format!("<strong>{}</strong>", &SETTINGS.app.name),
"pagetop" => "<a href=\"https://pagetop-rs\">PageTop</a>"
])) }
}))
)
.add_to("content", Container::prepare()
.add(Container::row()
.add(Container::column()
.with_id("visitors")
.add(Chunck::markup(html! {
h2 { (l("title_normal_user")) }
p { (l("text1_normal_user")) }
p { (l("text2_normal_user")) }
})))
.add(Container::column()
.with_id("pagetop")
.add(Chunck::markup(html! {
h2 { (l("title_about_pagetop")) }
p { (l("text1_about_pagetop")) }
p { (l("text2_about_pagetop")) }
h2 { (l("title_promo_pagetop")) }
p { (e("text1_promo_pagetop", &args![
"pagetop" =>
"<a href=\"https://pagetop-rs\">PageTop</a>"
])) }
}))
)
)
)
.add_to("content", Container::prepare()
.with_id("reporting")
.add(Chunck::markup(html! {
h2 { (l("title_report_problems")) }
p { (l("text1_report_problems")) }
p { (l("text2_report_problems")) }
}))
)
.render()
}

View file

@ -1,5 +0,0 @@
pub mod admin;
pub mod homepage;
#[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))]
pub mod user;

View file

@ -1 +0,0 @@
pub mod user;

View file

@ -1,18 +0,0 @@
use crate::db::entity::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Deserialize, Serialize)]
#[sea_orm(table_name = "user")]
pub struct Model {
#[sea_orm(primary_key)]
#[serde(skip_deserializing)]
pub id: i32,
pub title: String,
#[sea_orm(column_type = "Text")]
pub text: String,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
impl ActiveModelBehavior for ActiveModel {}

View file

@ -1,8 +0,0 @@
module_fullname = User
module_description = Manages the user registration and login system.
username = User name
password = Password
username_help = Enter your { $app } username.
password_help = Enter the password that accompanies your username.
login = Log in

View file

@ -1,8 +0,0 @@
module_fullname = Usuario
module_description = Gestion el registro de usuarios y el sistema de accesos.
username = Nombre de usuario
password = Contraseña
username_help = Introduzca su nombre de usuario en { $app }.
password_help = Introduzca la contraseña asociada a su nombre de usuario.
login = Iniciar sesión

View file

@ -1,54 +0,0 @@
use crate::db::migration::*;
#[derive(Iden)]
enum User {
Table,
Id,
Title,
Text,
}
pub struct Migration;
impl MigrationName for Migration {
fn name(&self) -> &str {
"m20220312_000001_create_table_user"
}
}
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.create_table(
Table::create()
.table(User::Table)
.if_not_exists()
.col(ColumnDef::new(User::Id)
.integer()
.not_null()
.auto_increment()
.primary_key(),
)
.col(ColumnDef::new(User::Title)
.string()
.not_null()
)
.col(ColumnDef::new(User::Text)
.string()
.not_null()
)
.to_owned()
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_table(Table::drop()
.table(User::Table)
.to_owned()
)
.await
}
}

View file

@ -1,12 +0,0 @@
use crate::db::migration::*;
pub mod m20220312_000001_create_table_user;
pub struct Migrator;
#[async_trait::async_trait]
impl MigratorTrait for Migrator {
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
vec![Box::new(m20220312_000001_create_table_user::Migration)]
}
}

View file

@ -1,61 +0,0 @@
use crate::prelude::*;
localize!("en-US", "src/base/module/user/locales");
mod entity;
mod migration;
pub struct UserModule;
impl Module for UserModule {
fn name(&self) -> &'static str {
"user"
}
fn fullname(&self) -> String {
l("module_fullname")
}
fn description(&self) -> Option<String> {
Some(l("module_description"))
}
fn configure_module(&self, cfg: &mut server::web::ServiceConfig) {
cfg.route("/user/login", server::web::get().to(login));
}
fn migrations(&self, dbconn: &db::DbConn) -> Result<(), db::DbErr> {
db_migrations!(dbconn)
}
}
fn form_login() -> impl PageComponent {
Form::prepare()
.with_id("user-login")
.add(form::Input::textfield()
.with_name("name")
.with_label(l("username").as_str())
.with_help_text(t("username_help", &args![
"app" => SETTINGS.app.name.to_owned()
]).as_str())
.autofocus(true)
)
.add(form::Input::password()
.with_name("pass")
.with_label(l("password").as_str())
.with_help_text(l("password_help").as_str())
)
.add(form::Button::submit(l("login").as_str()))
}
async fn login() -> server::Result<Markup> {
Page::prepare()
.with_title(
"Identificación del usuario"
)
.add_to("content", Container::prepare()
.with_id("welcome")
.add(form_login())
)
.render()
}

View file

@ -1,3 +1,4 @@
pub mod aliner;
pub mod minimal;
pub mod bootsier;
pub mod bulmix;

View file

@ -0,0 +1,30 @@
use crate::prelude::*;
pub const ALINER_THEME: &str = "pagetop::theme::aliner";
include!(concat!(env!("OUT_DIR"), "/aliner.rs"));
pub struct Aliner;
impl ThemeTrait for Aliner {
fn handler(&self) -> &'static str {
ALINER_THEME
}
fn configure_service(&self, cfg: &mut app::web::ServiceConfig) {
theme_static_files!(cfg, "/aliner");
}
fn before_render_page(&self, page: &mut Page) {
page.context()
.with_favicon(Some(Favicon::new()
.with_icon("/theme/favicon.png")
))
.add_stylesheet(
StyleSheet::with_source(
"/aliner/css/styles.css"
)
.with_weight(-99)
);
}
}

View file

@ -1,32 +0,0 @@
use crate::prelude::*;
include!(concat!(env!("OUT_DIR"), "/aliner.rs"));
pub struct AlinerTheme;
impl Theme for AlinerTheme {
fn name(&self) -> &'static str {
"aliner"
}
fn fullname(&self) -> String {
"Aliner".to_owned()
}
fn configure_theme(&self, cfg: &mut server::web::ServiceConfig) {
cfg.service(actix_web_static_files::ResourceFiles::new(
"/aliner",
assets()
));
}
fn before_render_page(&self, page: &mut Page) {
page.assets()
.add_stylesheet(
StyleSheet::source(
"/aliner/css/styles.css"
)
.with_weight(-99)
);
}
}

View file

@ -1,41 +1,35 @@
use crate::prelude::*;
pub const BOOTSIER_THEME: &str = "pagetop::theme::bootsier";
include!(concat!(env!("OUT_DIR"), "/bootsier.rs"));
localize!("en-US", "src/base/theme/bootsier/locales");
localize!("src/base/theme/bootsier/locales");
pub struct BootsierTheme;
pub struct Bootsier;
impl Theme for BootsierTheme {
fn name(&self) -> &'static str {
"bootsier"
impl ThemeTrait for Bootsier {
fn handler(&self) -> &'static str {
BOOTSIER_THEME
}
fn fullname(&self) -> String {
"Bootsier".to_owned()
}
fn configure_theme(&self, cfg: &mut server::web::ServiceConfig) {
cfg.service(actix_web_static_files::ResourceFiles::new(
"/bootsier",
assets()
));
fn configure_service(&self, cfg: &mut app::web::ServiceConfig) {
theme_static_files!(cfg, "/bootsier");
}
fn before_render_page(&self, page: &mut Page) {
page.assets()
.with_favicon(
Favicon::new()
.with_icon("/bootsier/favicon.png")
)
page.context()
.with_favicon(Some(Favicon::new()
.with_icon("/theme/favicon.png")
))
.add_stylesheet(
StyleSheet::source(
StyleSheet::with_source(
"/bootsier/css/bootstrap.min.css?ver=5.1.3"
)
.with_weight(-99)
)
.add_javascript(
JavaScript::source(
JavaScript::with_source(
"/bootsier/js/bootstrap.bundle.min.js?ver=5.1.3"
)
.with_weight(-99)
@ -43,25 +37,25 @@ impl Theme for BootsierTheme {
.add_jquery();
}
fn render_error_page(&self, mut s: server::http::StatusCode) -> server::Result<Markup> {
fn render_error_page(&self, mut s: app::http::StatusCode) -> app::Result<Markup> {
let mut description = "e500-description";
let mut message = "e500-description";
match s {
server::http::StatusCode::NOT_FOUND => {
app::http::StatusCode::NOT_FOUND => {
description = "e404-description";
message = "e404-message";
},
_ => {
s = server::http::StatusCode::INTERNAL_SERVER_ERROR;
s = app::http::StatusCode::INTERNAL_SERVER_ERROR;
}
}
Page::prepare()
Page::new()
.with_title(format!("Error {}", s.as_str()).as_str())
.add_to("content", Chunck::markup(html! {
.add_to("content", Chunck::with(html! {
div class="jumbotron" {
div class="media" {
img
src="/bootsier/images/caution.png"
src="/static/bootsier/images/caution.png"
class="mr-4"
style="width: 20%; max-width: 188px"
alt="Caution!";

View file

@ -0,0 +1,78 @@
use crate::prelude::*;
pub const BULMIX_THEME: &str = "pagetop::theme::bulmix";
include!(concat!(env!("OUT_DIR"), "/bulmix.rs"));
pub struct Bulmix;
impl ThemeTrait for Bulmix {
fn handler(&self) -> &'static str {
BULMIX_THEME
}
fn configure_service(&self, cfg: &mut app::web::ServiceConfig) {
theme_static_files!(cfg, "/bulmix");
}
fn before_render_page(&self, page: &mut Page) {
page.context()
.with_favicon(Some(Favicon::new()
.with_icon("/theme/favicon.png")
))
.add_stylesheet(
StyleSheet::with_source(
"/bulmix/css/bulma.min.css?ver=0.9.3"
)
.with_weight(-99)
)
.add_jquery();
}
fn before_render_component(
&self,
component: &mut dyn ComponentTrait,
_context: &mut InContext
) {
match component.handler() {
HEADING_COMPONENT => {
let h = component_mut::<Heading>(component);
h.alter_classes(concat_string!("title ", match h.display() {
HeadingDisplay::XxLarge => "is-1",
HeadingDisplay::Large => "is-2",
HeadingDisplay::Medium => "is-3",
HeadingDisplay::Small => "is-4",
HeadingDisplay::XxSmall => "is-5",
HeadingDisplay::Normal => "",
}).as_str(), ClassesOp::SetDefault);
},
PARAGRAPH_COMPONENT => {
let p = component_mut::<Paragraph>(component);
p.alter_classes(match p.display() {
ParagraphDisplay::XxLarge => "is-size-2",
ParagraphDisplay::Large => "is-size-3",
ParagraphDisplay::Medium => "is-size-4",
ParagraphDisplay::Small => "is-size-5",
ParagraphDisplay::XxSmall => "is-size-6",
ParagraphDisplay::Normal => "",
}, ClassesOp::SetDefault);
},
ANCHOR_COMPONENT => {
let a = component_mut::<Anchor>(component);
a.alter_classes(match a.anchor_type() {
AnchorType::Button => "button is-primary",
_ => "",
}, ClassesOp::SetDefault);
},
grid::ROW_COMPONENT => {
let row = component_mut::<grid::Row>(component);
row.alter_classes("columns", ClassesOp::SetDefault);
},
grid::COLUMN_COMPONENT => {
let col = component_mut::<grid::Column>(component);
col.alter_classes("column content", ClassesOp::SetDefault);
},
_ => {},
}
}
}

View file

@ -0,0 +1,11 @@
use crate::prelude::*;
pub const MINIMAL_THEME: &str = "pagetop::theme::minimal";
pub struct Minimal;
impl ThemeTrait for Minimal {
fn handler(&self) -> &'static str {
MINIMAL_THEME
}
}

View file

@ -1,13 +0,0 @@
use crate::prelude::*;
pub struct MinimalTheme;
impl Theme for MinimalTheme {
fn name(&self) -> &'static str {
"minimal"
}
fn fullname(&self) -> String {
"Minimal".to_owned()
}
}

View file

@ -45,15 +45,15 @@ pub static CONFIG: Lazy<Config> = Lazy::new(|| {
/// seguros. Produce un *panic!* en caso de asignaciones no válidas.
macro_rules! config_map {
(
$COMM:expr,
$CONF:ident,
$TYPE:tt
$doc:expr,
$SETTINGS:ident,
$Type:tt
$(, $key:expr => $value:expr)*
) => {
$crate::doc_comment! {
concat!($COMM),
concat!($doc),
pub static $CONF: $crate::Lazy<$TYPE> = $crate::Lazy::new(|| {
pub static $SETTINGS: $crate::Lazy<$Type> = $crate::Lazy::new(|| {
let mut settings = $crate::config::CONFIG.clone();
$(
settings.set_default($key, $value).unwrap();
@ -104,12 +104,18 @@ pub struct Webserver {
pub bind_port : u16,
}
#[derive(Debug, Deserialize)]
pub struct Dev {
pub static_files : String,
}
#[derive(Debug, Deserialize)]
pub struct Settings {
pub app : App,
pub log : Log,
pub database : Database,
pub webserver : Webserver,
pub dev : Dev,
}
config_map!(r#"
@ -124,7 +130,7 @@ Ajustes globales y valores predeterminados para las secciones *\[app\]*,
"app.theme" => "Bootsier",
"app.language" => "en-US",
"app.direction" => "ltr",
"app.startup_banner" => "Small",
"app.startup_banner" => "Slant",
// [log]
"log.tracing" => "Info",
@ -144,5 +150,8 @@ Ajustes globales y valores predeterminados para las secciones *\[app\]*,
// [webserver]
"webserver.bind_address" => "localhost",
"webserver.bind_port" => 8088
"webserver.bind_port" => 8088,
// [dev]
"dev.static_files" => ""
);

4
pagetop/src/core.rs Normal file
View file

@ -0,0 +1,4 @@
pub mod component; // API to build new components.
pub mod hook; // API to define functions that alter the behavior of PageTop core.
pub mod module; // API to add new features with modules.
pub mod theme; // API to create themes.

View file

@ -0,0 +1,28 @@
mod hook;
pub use hook::{
BEFORE_RENDER_COMPONENT_HOOK,
BeforeRenderComponentHook,
};
mod context;
pub use context::InContext;
mod definition;
pub use definition::{
AnyComponent,
ComponentTrait,
component_ref,
component_mut,
};
use definition::render_component;
mod bundle;
pub use bundle::ComponentsBundle;
mod all;
pub use all::add_component_to;
pub(crate) use all::common_components;
pub fn render_always() -> bool { true }
pub fn render_never() -> bool { false }

View file

@ -0,0 +1,22 @@
use crate::Lazy;
use super::{ComponentsBundle, ComponentTrait};
use std::sync::RwLock;
use std::collections::HashMap;
static COMPONENTS: Lazy<RwLock<HashMap<&str, ComponentsBundle>>> = Lazy::new(|| {
RwLock::new(HashMap::new())
});
pub fn add_component_to(region: &'static str, component: impl ComponentTrait) {
let mut hmap = COMPONENTS.write().unwrap();
if let Some(regions) = hmap.get_mut(region) {
regions.add(component);
} else {
hmap.insert(region, ComponentsBundle::new_with(component));
}
}
pub fn common_components() -> HashMap<&'static str, ComponentsBundle> {
COMPONENTS.read().unwrap().clone()
}

View file

@ -0,0 +1,37 @@
use crate::html::{Markup, html};
use super::{InContext, ComponentTrait};
use std::sync::{Arc, RwLock};
#[derive(Clone)]
pub struct ComponentsBundle(Vec<Arc<RwLock<dyn ComponentTrait>>>);
impl ComponentsBundle {
pub fn new() -> Self {
ComponentsBundle(Vec::new())
}
pub fn new_with(component: impl ComponentTrait) -> Self {
let mut container = ComponentsBundle::new();
container.add(component);
container
}
pub fn add(&mut self, component: impl ComponentTrait) {
self.0.push(Arc::new(RwLock::new(component)));
}
pub fn clear(&mut self) {
self.0.clear();
}
pub fn render(&self, context: &mut InContext) -> Markup {
let mut components = self.0.clone();
components.sort_by_key(|c| c.read().unwrap().weight());
html! {
@for c in components.iter() {
(super::render_component(&mut *c.write().unwrap(), context))
}
}
}
}

View file

@ -0,0 +1,118 @@
use crate::{Lazy, base, concat_string, util};
use crate::config::SETTINGS;
use crate::html::*;
use crate::core::theme::ThemeTrait;
use crate::core::theme::all::theme_by_single_name;
static DEFAULT_THEME: Lazy<&dyn ThemeTrait> = Lazy::new(|| {
match theme_by_single_name(&SETTINGS.app.theme) {
Some(theme) => theme,
None => &base::theme::bootsier::Bootsier,
}
});
pub struct InContext {
theme : &'static dyn ThemeTrait,
favicon : Option<Favicon>,
metadata : Vec<(String, String)>,
stylesheets: Assets<StyleSheet>,
javascripts: Assets<JavaScript>,
with_jquery: bool,
id_counter : usize,
}
impl InContext {
pub fn new() -> Self {
InContext {
theme : *DEFAULT_THEME,
favicon : None,
metadata : Vec::new(),
stylesheets: Assets::<StyleSheet>::new(),
javascripts: Assets::<JavaScript>::new(),
with_jquery: false,
id_counter : 0,
}
}
pub fn using_theme(&mut self, theme_name: &str) -> &mut Self {
self.theme = theme_by_single_name(theme_name).unwrap_or(*DEFAULT_THEME);
self
}
pub fn with_favicon(&mut self, favicon: Option<Favicon>) -> &mut Self {
self.favicon = favicon;
self
}
pub fn add_metadata(&mut self, name: String, content: String) -> &mut Self {
self.metadata.push((name, content));
self
}
pub fn add_stylesheet(&mut self, css: StyleSheet) -> &mut Self {
self.stylesheets.add(css);
self
}
pub fn add_javascript(&mut self, js: JavaScript) -> &mut Self {
self.javascripts.add(js);
self
}
pub fn add_jquery(&mut self) -> &mut Self {
if !self.with_jquery {
self.add_javascript(
JavaScript::with_source(
"/theme/js/jquery.min.js?ver=3.6.0"
)
.with_weight(isize::MIN)
.with_mode(JSMode::Normal)
);
self.with_jquery = true;
}
self
}
/// InContext GETTERS.
pub(crate) fn theme(&mut self) -> &'static dyn ThemeTrait {
self.theme
}
/// InContext RENDER.
pub fn render(&mut self) -> Markup {
html! {
@match &self.favicon {
Some(favicon) => (favicon.render()),
None => "",
}
@for (name, content) in &self.metadata {
meta name=(name) content=(content) {}
}
(self.stylesheets.render())
(self.javascripts.render())
}
}
// InContext EXTRAS.
pub fn required_id<T>(&mut self, id: &Option<String>) -> String {
match id {
Some(id) => id.to_string(),
None => {
let prefix = util::single_type_name::<T>()
.trim()
.replace(" ", "_")
.to_lowercase();
let prefix = if prefix.is_empty() {
"prefix".to_owned()
} else {
prefix
};
self.id_counter += 1;
concat_string!(prefix, "-", self.id_counter.to_string())
}
}
}
}

View file

@ -0,0 +1,66 @@
use crate::util;
use crate::html::{Markup, html};
use crate::core::hook::{hook_ref, run_hooks};
use super::{BEFORE_RENDER_COMPONENT_HOOK, BeforeRenderComponentHook, InContext};
pub use std::any::Any as AnyComponent;
pub trait ComponentTrait: AnyComponent + Send + Sync {
fn new() -> Self where Self: Sized;
fn handler(&self) -> &'static str;
fn name(&self) -> String {
util::single_type_name::<Self>().to_owned()
}
fn description(&self) -> Option<String> {
None
}
fn is_renderable(&self) -> bool {
true
}
fn weight(&self) -> isize {
0
}
#[allow(unused_variables)]
fn default_render(&self, context: &mut InContext) -> Markup {
html! {}
}
fn as_ref_any(&self) -> &dyn AnyComponent;
fn as_mut_any(&mut self) -> &mut dyn AnyComponent;
}
pub fn component_ref<C: 'static>(component: &dyn ComponentTrait) -> &C {
component.as_ref_any().downcast_ref::<C>().unwrap()
}
pub fn component_mut<C: 'static>(component: &mut dyn ComponentTrait) -> &mut C {
component.as_mut_any().downcast_mut::<C>().unwrap()
}
pub fn render_component(component: &mut dyn ComponentTrait, context: &mut InContext) -> Markup {
// Acciones de los módulos antes de renderizar el componente.
run_hooks(
BEFORE_RENDER_COMPONENT_HOOK,
|hook| hook_ref::<BeforeRenderComponentHook>(&**hook).run(component, context)
);
// Acciones del tema antes de renderizar el componente.
context.theme().before_render_component(component, context);
match component.is_renderable() {
true => {
match context.theme().render_component(component, context) {
Some(html) => html,
None => component.default_render(context)
}
},
false => html! {}
}
}

View file

@ -0,0 +1,48 @@
use crate::core::hook::{HookTrait, AnyHook};
use super::{ComponentTrait, InContext};
pub const BEFORE_RENDER_COMPONENT_HOOK: &str = "pagetop::hook::before_render_component";
pub struct BeforeRenderComponentHook {
hook: Option<fn(&mut dyn ComponentTrait, &mut InContext)>,
weight: isize,
}
impl HookTrait for BeforeRenderComponentHook {
fn new() -> Self {
BeforeRenderComponentHook {
hook: None,
weight: 0,
}
}
fn handler(&self) -> &'static str {
BEFORE_RENDER_COMPONENT_HOOK
}
fn weight(&self) -> isize {
self.weight
}
fn as_ref_any(&self) -> &dyn AnyHook {
self
}
}
impl BeforeRenderComponentHook {
pub fn with_hook(mut self, hook: fn(&mut dyn ComponentTrait, &mut InContext)) -> Self {
self.hook = Some(hook);
self
}
pub fn with_weight(mut self, weight: isize) -> Self {
self.weight = weight;
self
}
pub fn run(&self, component: &mut dyn ComponentTrait, context: &mut InContext) {
if let Some(hook) = self.hook {
hook(component, context)
}
}
}

View file

@ -1,61 +0,0 @@
use crate::{Lazy, trace};
use crate::core::theme::Theme;
use crate::core::module::Module;
use crate::core::response::page::PageContainer;
use crate::core::server;
use std::sync::RwLock;
use std::collections::HashMap;
include!(concat!(env!("OUT_DIR"), "/theme.rs"));
// -----------------------------------------------------------------------------
// Temas registrados y tema por defecto.
// -----------------------------------------------------------------------------
pub static THEMES: Lazy<RwLock<Vec<&dyn Theme>>> = Lazy::new(|| {
RwLock::new(Vec::new())
});
pub fn themes(cfg: &mut server::web::ServiceConfig) {
cfg.service(actix_web_static_files::ResourceFiles::new(
"/theme",
assets()
));
for t in THEMES.read().unwrap().iter() {
t.configure_theme(cfg);
}
}
// -----------------------------------------------------------------------------
// Módulos registrados.
// -----------------------------------------------------------------------------
pub static MODULES: Lazy<RwLock<Vec<&dyn Module>>> = Lazy::new(|| {
RwLock::new(Vec::new())
});
pub fn modules(cfg: &mut server::web::ServiceConfig) {
for m in MODULES.read().unwrap().iter() {
m.configure_module(cfg);
}
}
#[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))]
pub fn run_migrations() {
trace::info!("Checking migrations.");
for m in MODULES.read().unwrap().iter() {
m.migrations(
&*server::db::DBCONN.read().unwrap()
).expect("Failed to run migrations");
}
}
// -----------------------------------------------------------------------------
// Componentes globales.
// -----------------------------------------------------------------------------
pub static COMPONENTS: Lazy<RwLock<HashMap<&str, PageContainer>>> = Lazy::new(
|| { RwLock::new(HashMap::new()) }
);

14
pagetop/src/core/hook.rs Normal file
View file

@ -0,0 +1,14 @@
mod definition;
pub use definition::{
HookTrait,
AnyHook,
hook_ref,
};
mod holder;
pub use holder::HookItem;
use holder::HooksHolder;
mod all;
pub use all::run_hooks;
pub(crate) use all::add_hook;

View file

@ -0,0 +1,26 @@
use crate::Lazy;
use super::{HookItem, HooksHolder};
use std::sync::RwLock;
use std::collections::HashMap;
// Registered actions.
static ACTIONS: Lazy<RwLock<HashMap<&str, HooksHolder>>> = Lazy::new(|| {
RwLock::new(HashMap::new())
});
pub fn add_hook(hook: HookItem) {
let mut hmap = ACTIONS.write().unwrap();
let action_handler = hook.handler();
if let Some(actions) = hmap.get_mut(action_handler) {
actions.add(hook);
} else {
hmap.insert(action_handler, HooksHolder::new_with(hook));
}
}
pub fn run_hooks<B, F>(action_handler: &str, f: F) where F: FnMut(&HookItem) -> B {
if let Some(actions) = ACTIONS.read().unwrap().get(action_handler) {
actions.iter_map(f)
}
}

View file

@ -0,0 +1,17 @@
pub use std::any::Any as AnyHook;
pub trait HookTrait: AnyHook + Send + Sync {
fn new() -> Self where Self: Sized;
fn handler(&self) -> &'static str;
fn weight(&self) -> isize {
0
}
fn as_ref_any(&self) -> &dyn AnyHook;
}
pub fn hook_ref<A: 'static>(hook: &dyn HookTrait) -> &A {
hook.as_ref_any().downcast_ref::<A>().unwrap()
}

View file

@ -0,0 +1,36 @@
use super::HookTrait;
use std::sync::{Arc, RwLock};
pub type HookItem = Box<dyn HookTrait>;
#[macro_export]
macro_rules! hook_item {
( $hook:ident => $f:ident $(, $weight:expr)? ) => {{
Box::new($hook::new().with_hook($f)$(.with_weight($weight))?)
}};
}
pub struct HooksHolder(Arc<RwLock<Vec<HookItem>>>);
impl HooksHolder {
pub fn new() -> Self {
HooksHolder(Arc::new(RwLock::new(Vec::new())))
}
pub fn new_with(hook: HookItem) -> Self {
let mut container = HooksHolder::new();
container.add(hook);
container
}
pub fn add(&mut self, hook: HookItem) {
let mut actions = self.0.write().unwrap();
actions.push(hook);
actions.sort_by_key(|a| a.weight());
}
pub fn iter_map<B, F>(&self, f: F) where Self: Sized, F: FnMut(&HookItem) -> B {
let _: Vec<_> = self.0.read().unwrap().iter().map(f).collect();
}
}

View file

@ -1,8 +0,0 @@
pub use actix_web::dev::Server;
mod global;
pub mod theme;
pub mod module;
pub mod response;
pub mod server;

View file

@ -0,0 +1,7 @@
mod definition;
pub use definition::{
BaseModule,
ModuleTrait,
};
pub(crate) mod all;

View file

@ -0,0 +1,84 @@
use crate::{Lazy, app, trace};
use crate::core::hook::add_hook;
use super::ModuleTrait;
#[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))]
use crate::{
db::*,
run_now,
};
use std::sync::RwLock;
// Enabled modules.
static ENABLED_MODULES: Lazy<RwLock<Vec<&dyn ModuleTrait>>> = Lazy::new(|| {
RwLock::new(Vec::new())
});
/* Disabled modules.
static DISABLED_MODULES: Lazy<RwLock<Vec<&dyn ModuleTrait>>> = Lazy::new(|| {
RwLock::new(Vec::new())
}); */
pub fn enable_modules(modules: Vec<&'static dyn ModuleTrait>) {
for m in modules {
enable(m)
}
}
fn enable(module: &'static dyn ModuleTrait) {
let mut list: Vec<&dyn ModuleTrait> = Vec::new();
add_to(&mut list, module);
list.reverse();
ENABLED_MODULES.write().unwrap().append(&mut list);
}
fn add_to(list: &mut Vec<&dyn ModuleTrait>, module: &'static dyn ModuleTrait) {
if !ENABLED_MODULES.read().unwrap().iter().any(|m| m.handler() == module.handler()) {
if !list.iter().any(|m| m.handler() == module.handler()) {
trace::debug!("Enabling module \"{}\"", module.single_name());
list.push(module);
let mut dependencies = module.dependencies();
dependencies.reverse();
for d in dependencies.iter() {
add_to(list, *d);
}
}
}
}
/*
#[allow(unused_variables)]
pub fn disable_module(module: &'static dyn ModuleTrait) {
}
*/
pub fn modules(cfg: &mut app::web::ServiceConfig) {
for m in ENABLED_MODULES.read().unwrap().iter() {
m.configure_service(cfg);
}
}
pub fn register_hooks() {
for m in ENABLED_MODULES.read().unwrap().iter() {
for a in m.actions().into_iter() {
add_hook(a);
}
}
}
#[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))]
pub fn run_migrations() {
run_now({
struct Migrator;
impl MigratorTrait for Migrator {
fn migrations() -> Vec<MigrationItem> {
let mut migrations = vec![];
for m in ENABLED_MODULES.read().unwrap().iter() {
migrations.append(&mut m.migrations());
}
migrations
}
}
Migrator::up(&app::db::DBCONN, None)
}).unwrap();
}

View file

@ -1,25 +1,46 @@
use crate::core::server;
use crate::{app, util};
use crate::core::hook::HookItem;
#[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))]
use crate::db;
use crate::db::MigrationItem;
pub trait BaseModule {
fn single_name(&self) -> &'static str;
}
/// Los módulos deben implementar este *trait*.
pub trait Module: Send + Sync {
fn name(&self) -> &'static str;
pub trait ModuleTrait: BaseModule + Send + Sync {
fn handler(&self) -> &'static str;
fn fullname(&self) -> String;
fn name(&self) -> String {
self.single_name().to_owned()
}
fn description(&self) -> Option<String> {
None
}
fn dependencies(&self) -> Vec<&'static dyn ModuleTrait> {
vec![]
}
#[allow(unused_variables)]
fn configure_module(&self, cfg: &mut server::web::ServiceConfig) {
fn configure_service(&self, cfg: &mut app::web::ServiceConfig) {
}
fn actions(&self) -> Vec<HookItem> {
vec![]
}
#[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))]
#[allow(unused_variables)]
fn migrations(&self, dbconn: &db::DbConn) -> Result<(), db::DbErr> {
Ok(())
fn migrations(&self) -> Vec<MigrationItem> {
vec![]
}
}
impl<M: ?Sized + ModuleTrait> BaseModule for M {
fn single_name(&self) -> &'static str {
util::single_type_name::<Self>()
}
}

View file

@ -1,16 +0,0 @@
use crate::core::global;
mod definition;
pub use definition::Module;
pub fn register_module(m: &'static (dyn Module + 'static)) {
global::MODULES.write().unwrap().push(m);
}
pub fn find_module(name: &str) -> Option<&'static (dyn Module + 'static)> {
let modules = global::MODULES.write().unwrap();
match modules.iter().find(|t| t.name() == name) {
Some(module) => Some(*module),
_ => None,
}
}

View file

@ -1,308 +0,0 @@
use crate::{Lazy, base};
use crate::config::SETTINGS;
use crate::core::global;
use crate::core::theme::{Markup, PreEscaped, Theme, find_theme, html};
static DEFAULT_THEME: Lazy<&dyn Theme> = Lazy::new(|| {
for t in global::THEMES.read().unwrap().iter() {
if t.name().to_lowercase() == SETTINGS.app.theme.to_lowercase() {
return *t;
}
}
&base::theme::bootsier::BootsierTheme
});
// -----------------------------------------------------------------------------
// Favicon.
// -----------------------------------------------------------------------------
pub struct Favicon(Vec<String>);
impl Favicon {
pub fn new() -> Self {
Favicon(Vec::new())
}
pub fn with_icon(self, image: &str) -> Self {
self.add_item("icon", image, "", "")
}
pub fn with_icon_for_sizes(self, image: &str, sizes: &str) -> Self {
self.add_item("icon", image, sizes, "")
}
pub fn with_apple_touch_icon(self, image: &str, sizes: &str) -> Self {
self.add_item("apple-touch-icon", image, sizes, "")
}
pub fn with_mask_icon(self, image: &str, color: &str) -> Self {
self.add_item("mask-icon", image, "", color)
}
pub fn with_manifest(self, file: &str) -> Self {
self.add_item("manifest", file, "", "")
}
pub fn with_theme_color(mut self, color: &str) -> Self {
self.0.push(format!(
"<meta name=\"theme-color\" content=\"{}\">", color
));
self
}
pub fn with_ms_tile_color(mut self, color: &str) -> Self {
self.0.push(format!(
"<meta name=\"msapplication-TileColor\" content=\"{}\">", color
));
self
}
pub fn with_ms_tile_image(mut self, image: &str) -> Self {
self.0.push(format!(
"<meta name=\"msapplication-TileImage\" content=\"{}\">", image
));
self
}
fn add_item(
mut self,
rel : &str,
source: &str,
sizes : &str,
color : &str
) -> Self {
let mut link: String = format!("<link rel=\"{}\"", rel);
if let Some(i) = source.rfind('.') {
link = match source[i..].to_owned().to_lowercase().as_str() {
".gif" => format!("{} type=\"image/gif\"", link),
".ico" => format!("{} type=\"image/x-icon\"", link),
".jpg" => format!("{} type=\"image/jpg\"", link),
".png" => format!("{} type=\"image/png\"", link),
".svg" => format!("{} type=\"image/svg+xml\"", link),
_ => link
};
}
if !sizes.is_empty() {
link = format!("{} sizes=\"{}\"", link, sizes);
}
if !color.is_empty() {
link = format!("{} color=\"{}\"", link, color);
}
self.0.push(format!("{} href=\"{}\">", link, source));
self
}
fn render(&self) -> Markup {
html! {
@for item in &self.0 {
(PreEscaped(item))
}
}
}
}
// -----------------------------------------------------------------------------
// StyleSheet.
// -----------------------------------------------------------------------------
pub struct StyleSheet {
source: &'static str,
weight: i8,
}
impl StyleSheet {
pub fn source(s: &'static str) -> Self {
StyleSheet {
source: s,
weight: 0,
}
}
pub fn with_weight(mut self, weight: i8) -> Self {
self.weight = weight;
self
}
pub fn weight(self) -> i8 {
self.weight
}
fn render(&self) -> Markup {
html! {
link rel="stylesheet" href=(self.source);
}
}
}
// -----------------------------------------------------------------------------
// JavaScript.
// -----------------------------------------------------------------------------
#[derive(PartialEq)]
pub enum JSMode { Async, Defer, Normal }
pub struct JavaScript {
source: &'static str,
weight: i8,
mode : JSMode,
}
impl JavaScript {
pub fn source(s: &'static str) -> Self {
JavaScript {
source: s,
weight: 0,
mode : JSMode::Defer,
}
}
pub fn with_weight(mut self, weight: i8) -> Self {
self.weight = weight;
self
}
pub fn with_mode(mut self, mode: JSMode) -> Self {
self.mode = mode;
self
}
pub fn weight(self) -> i8 {
self.weight
}
fn render(&self) -> Markup {
html! {
script type="text/javascript"
src=(self.source)
async[self.mode == JSMode::Async]
defer[self.mode == JSMode::Defer]
{};
}
}
}
// -----------------------------------------------------------------------------
// Page assets.
// -----------------------------------------------------------------------------
pub struct PageAssets {
theme : &'static dyn Theme,
favicon : Option<Favicon>,
metadata : Vec<(String, String)>,
stylesheets: Vec<StyleSheet>,
javascripts: Vec<JavaScript>,
with_jquery: bool,
id_counter : u32,
}
impl PageAssets {
pub fn new() -> Self {
PageAssets {
theme : *DEFAULT_THEME,
favicon : None,
metadata : Vec::new(),
stylesheets: Vec::new(),
javascripts: Vec::new(),
with_jquery: false,
id_counter : 0,
}
}
pub fn using_theme(&mut self, theme_name: &str) -> &mut Self {
self.theme = find_theme(theme_name).unwrap_or(*DEFAULT_THEME);
self
}
pub fn with_favicon(&mut self, favicon: Favicon) -> &mut Self {
self.favicon = Some(favicon);
self
}
pub fn add_metadata(&mut self, name: String, content: String) -> &mut Self {
self.metadata.push((name, content));
self
}
pub fn add_stylesheet(&mut self, css: StyleSheet) -> &mut Self {
match self.stylesheets.iter().position(|x| x.source == css.source) {
Some(index) => if self.stylesheets[index].weight > css.weight {
self.stylesheets.remove(index);
self.stylesheets.push(css);
},
_ => self.stylesheets.push(css)
}
self
}
pub fn add_javascript(&mut self, js: JavaScript) -> &mut Self {
match self.javascripts.iter().position(|x| x.source == js.source) {
Some(index) => if self.javascripts[index].weight > js.weight {
self.javascripts.remove(index);
self.javascripts.push(js);
},
_ => self.javascripts.push(js)
}
self
}
pub fn add_jquery(&mut self) -> &mut Self {
if !self.with_jquery {
self.add_javascript(
JavaScript::source(
"/theme/js/jquery.min.js?ver=3.6.0"
)
.with_weight(i8::MIN)
.with_mode(JSMode::Normal)
);
self.with_jquery = true;
}
self
}
/// Assets GETTERS.
pub fn theme(&mut self) -> &'static dyn Theme {
self.theme
}
/// Assets RENDER.
pub fn render(&mut self) -> Markup {
let ordered_css = &mut self.stylesheets;
ordered_css.sort_by_key(|o| o.weight);
let ordered_js = &mut self.javascripts;
ordered_js.sort_by_key(|o| o.weight);
html! {
@match &self.favicon {
Some(favicon) => (favicon.render()),
None => "",
}
@for (name, content) in &self.metadata {
meta name=(name) content=(content) {}
}
@for css in ordered_css {
(css.render())
}
@for js in ordered_js {
(js.render())
}
}
}
// Assets EXTRAS.
pub fn serial_id(&mut self, prefix: &str, id: &str) -> String {
if id.is_empty() {
let prefix = prefix.trim().replace(" ", "_").to_lowercase();
let prefix = if prefix.is_empty() {
"prefix".to_owned()
} else {
prefix
};
self.id_counter += 1;
[prefix, self.id_counter.to_string()].join("-")
} else {
id.to_owned()
}
}
}

View file

@ -1,42 +0,0 @@
use crate::core::theme::{Markup, html};
use crate::core::response::page::PageAssets;
use downcast_rs::{Downcast, impl_downcast};
use std::any::type_name;
pub trait PageComponent: Downcast + Send + Sync {
fn prepare() -> Self where Self: Sized;
fn name(&self) -> &'static str {
let name = type_name::<Self>();
match name.rfind("::") {
Some(position) => &name[(position + 2)..],
None => name
}
}
fn fullname(&self) -> String {
type_name::<Self>().to_owned()
}
fn description(&self) -> Option<String> {
None
}
fn is_renderable(&self) -> bool {
true
}
fn weight(&self) -> i8 {
0
}
#[allow(unused_variables)]
fn default_render(&self, assets: &mut PageAssets) -> Markup {
html! {}
}
}
impl_downcast!(PageComponent);

View file

@ -1,33 +0,0 @@
use crate::core::theme::{Markup, html};
use crate::core::response::page::{PageAssets, PageComponent, render_component};
use std::sync::Arc;
#[derive(Clone)]
pub struct PageContainer(Vec<Arc<dyn PageComponent>>);
impl PageContainer {
pub fn new() -> Self {
PageContainer(Vec::new())
}
pub fn new_with(component: impl PageComponent) -> Self {
let mut container = PageContainer::new();
container.add(component);
container
}
pub fn add(&mut self, component: impl PageComponent) {
self.0.push(Arc::new(component));
}
pub fn render(&self, assets: &mut PageAssets) -> Markup {
let mut components = self.0.clone();
components.sort_by_key(|c| c.weight());
html! {
@for c in components.iter() {
(render_component(&**c, assets))
}
}
}
}

View file

@ -1,19 +0,0 @@
mod assets;
pub use assets::{
Favicon,
StyleSheet,
JavaScript, JSMode,
PageAssets,
};
mod component;
pub use component::PageComponent;
mod container;
pub use container::PageContainer;
mod page;
pub use page::Page;
pub use page::render_component;
pub use page::add_component_to;

View file

@ -1,217 +0,0 @@
use crate::{Lazy, trace, util};
use crate::config::SETTINGS;
use crate::core::{global, server};
use crate::core::theme::{DOCTYPE, Markup, html};
use crate::core::response::page::{PageAssets, PageComponent, PageContainer};
use std::borrow::Cow;
use std::collections::HashMap;
static DEFAULT_LANGUAGE: Lazy<Option<String>> = Lazy::new(|| {
let language = SETTINGS.app.language[..2].to_lowercase();
if !language.is_empty() {
Some(language)
} else {
None
}
});
static DEFAULT_DIRECTION: Lazy<Option<String>> = Lazy::new(|| {
let direction = SETTINGS.app.direction.to_lowercase();
match direction.as_str() {
"auto" => Some("auto".to_owned()),
"ltr" => Some("ltr".to_owned()),
"rtl" => Some("rtl".to_owned()),
"" => None,
_ => {
trace::warn!(
"Text direction \"{}\" not valid. {}.",
SETTINGS.app.direction,
"Check the settings file"
);
None
}
}
});
pub enum TextDirection { Auto, LeftToRight, RightToLeft }
pub struct Page<'a> {
language : Option<String>,
direction : Option<String>,
title : Option<String>,
description : Option<String>,
assets : PageAssets,
body_classes: Cow<'a, str>,
regions : HashMap<&'a str, PageContainer>,
template : String,
}
impl<'a> Page<'a> {
pub fn prepare() -> Self {
Page {
language : match &*DEFAULT_LANGUAGE {
Some(language) => Some(language.to_owned()),
_ => None,
},
direction : match &*DEFAULT_DIRECTION {
Some(direction) => Some(direction.to_owned()),
_ => None,
},
title : None,
description : None,
body_classes: "body".into(),
assets : PageAssets::new(),
regions : global::COMPONENTS.read().unwrap().clone(),
template : "default".to_owned(),
}
}
// Page BUILDER.
pub fn with_language(&mut self, language: &str) -> &mut Self {
self.language = util::optional_str(language);
self
}
pub fn with_direction(&mut self, dir: TextDirection) -> &mut Self {
self.direction = match dir {
TextDirection::Auto => Some("auto".to_owned()),
TextDirection::LeftToRight => Some("ltr".to_owned()),
TextDirection::RightToLeft => Some("rtl".to_owned()),
};
self
}
pub fn with_title(&mut self, title: &str) -> &mut Self {
self.title = util::optional_str(title);
self
}
pub fn with_description(&mut self, description: &str) -> &mut Self {
self.description = util::optional_str(description);
self
}
pub fn with_body_classes(&mut self, body_classes: &'a str) -> &mut Self {
self.body_classes = body_classes.into();
self
}
pub fn add_body_classes(&mut self, body_classes: &'a str) -> &mut Self {
self.body_classes = String::from(
format!("{} {}", self.body_classes, body_classes).trim()
).into();
self
}
pub fn add_to(
&mut self,
region: &'a str,
component: impl PageComponent
) -> &mut Self {
if let Some(regions) = self.regions.get_mut(region) {
regions.add(component);
} else {
self.regions.insert(region, PageContainer::new_with(component));
}
self
}
pub fn using_template(&mut self, template: &str) -> &mut Self {
self.template = template.to_owned();
self
}
// Page GETTERS.
pub fn language(&self) -> &str {
util::assigned_str(&self.language)
}
pub fn direction(&self) -> &str {
util::assigned_str(&self.direction)
}
pub fn title(&self) -> &str {
util::assigned_str(&self.title)
}
pub fn description(&self) -> &str {
util::assigned_str(&self.description)
}
pub fn body_classes(&self) -> &str {
if self.body_classes.is_empty() {
return "body";
}
&self.body_classes
}
pub fn assets(&mut self) -> &mut PageAssets {
&mut self.assets
}
pub fn template(&self) -> &str {
self.template.as_str()
}
// Page RENDER.
pub fn render(&mut self) -> server::Result<Markup> {
// Acciones del tema antes de renderizar la página.
self.assets.theme().before_render_page(self);
// Primero, renderizar el cuerpo.
let body = self.assets.theme().render_page_body(self);
// Luego, renderizar la cabecera.
let head = self.assets.theme().render_page_head(self);
// Finalmente, renderizar la página.
return Ok(html! {
(DOCTYPE)
html lang=[&self.language] dir=[&self.direction] {
(head)
(body)
}
})
}
pub fn render_region(&mut self, region: &str) -> Markup {
match self.regions.get_mut(region) {
Some(components) => components.render(&mut self.assets),
None => html! {}
}
}
// Page EXTRAS.
pub fn using_theme(&mut self, theme_name: &str) -> &mut Self {
self.assets.using_theme(theme_name);
self
}
}
pub fn render_component(
component: &dyn PageComponent,
assets: &mut PageAssets
) -> Markup {
match component.is_renderable() {
true => match assets.theme().render_component(component, assets) {
Some(markup) => markup,
None => component.default_render(assets)
},
false => html! {}
}
}
pub fn add_component_to(region: &'static str, component: impl PageComponent) {
let mut hmap = global::COMPONENTS.write().unwrap();
if let Some(regions) = hmap.get_mut(region) {
regions.add(component);
} else {
hmap.insert(region, PageContainer::new_with(component));
}
}

View file

@ -1,95 +0,0 @@
use crate::{Lazy, base, trace};
use crate::config::SETTINGS;
use crate::core::{Server, global, server};
use crate::core::theme::register_theme;
use crate::core::module::register_module;
use std::io::Error;
pub struct Application {
server: Server,
}
impl Application {
pub async fn build(bootstrap: Option<fn()>) -> Result<Self, Error> {
// Imprime un rótulo de presentación (opcional).
if SETTINGS.app.startup_banner.to_lowercase() != "off" {
let figfont = figlet_rs::FIGfont::from_content(
match SETTINGS.app.startup_banner.to_lowercase().as_str() {
"slant" => include_str!("figfonts/slant.flf"),
"small" => include_str!("figfonts/small.flf"),
"speed" => include_str!("figfonts/speed.flf"),
"starwars" => include_str!("figfonts/starwars.flf"),
_ => {
println!(
"FIGfont \"{}\" not found for banner. {}. {}.",
SETTINGS.app.startup_banner,
"Using \"Small\"",
"Check the settings file",
);
include_str!("figfonts/small.flf")
}
}
).unwrap();
println!("\n{} {}\n\n Powered by PageTop {}\n",
figfont.convert(&SETTINGS.app.name).unwrap(),
&SETTINGS.app.description,
env!("CARGO_PKG_VERSION")
);
}
// Inicia registro de trazas y eventos.
Lazy::force(&server::tracing::TRACING);
// Valida el identificador de idioma.
Lazy::force(&server::locale::LANGID);
// Conecta con la base de datos (opcional).
#[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))]
Lazy::force(&server::db::DBCONN);
// Registra los temas predefinidos.
register_theme(&base::theme::aliner::AlinerTheme);
register_theme(&base::theme::minimal::MinimalTheme);
register_theme(&base::theme::bootsier::BootsierTheme);
// Registra los módulos predeterminados.
register_module(&base::module::admin::AdminModule);
// Registra los módulos que requieren base de datos.
#[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))]
register_module(&base::module::user::UserModule);
// Ejecuta la función de inicio de la aplicación.
if bootstrap != None {
trace::info!("Calling application bootstrap.");
let _ = &(bootstrap.unwrap())();
}
// Registra el módulo para la página de inicio de PageTop.
// Al ser el último, puede sobrecargarse con la función de inicio.
register_module(&base::module::homepage::HomepageModule);
// Comprueba actualizaciones pendientes de la base de datos (opcional).
#[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))]
global::run_migrations();
// Prepara el servidor web.
let server = server::HttpServer::new(move || {
server::App::new()
.wrap(tracing_actix_web::TracingLogger::default())
.configure(&global::themes)
.configure(&global::modules)
})
.bind(format!("{}:{}",
&SETTINGS.webserver.bind_address,
&SETTINGS.webserver.bind_port
))?
.run();
Ok(Self { server })
}
pub fn run(self) -> Result<Server, Error> {
Ok(self.server)
}
}

View file

@ -1,60 +0,0 @@
use crate::{Lazy, db, run_now, trace};
use crate::config::SETTINGS;
use std::sync::RwLock;
use sea_orm::{ConnectOptions, Database};
use tracing_unwrap::ResultExt;
pub static DBCONN: Lazy<RwLock<db::DbConn>> = Lazy::new(|| {
trace::info!(
"Connecting to database \"{}\" using a pool of {} connections.",
&SETTINGS.database.db_name,
&SETTINGS.database.max_pool_size
);
let db_uri = match SETTINGS.database.db_type.as_str() {
"mysql" | "postgres" => {
let mut tmp_uri = db::DbUri::parse(format!(
"{}://{}/{}",
&SETTINGS.database.db_type,
&SETTINGS.database.db_host,
&SETTINGS.database.db_name
).as_str()).unwrap();
tmp_uri.set_username(
&SETTINGS.database.db_user.as_str()
).unwrap();
// https://github.com/launchbadge/sqlx/issues/1624
tmp_uri.set_password(
Some(&SETTINGS.database.db_pass.as_str())
).unwrap();
if SETTINGS.database.db_port != 0 {
tmp_uri.set_port(
Some(SETTINGS.database.db_port)
).unwrap();
}
tmp_uri
},
"sqlite" => db::DbUri::parse(
format!("{}://{}",
&SETTINGS.database.db_type,
&SETTINGS.database.db_name
).as_str()).unwrap(),
_ => {
trace::error!(
"Unrecognized database type \"{}\".",
&SETTINGS.database.db_type
);
db::DbUri::parse("").unwrap()
}
};
let db_conn = run_now(
Database::connect::<ConnectOptions>({
let mut db_opt = ConnectOptions::new(db_uri.to_string());
db_opt.max_connections(SETTINGS.database.max_pool_size);
db_opt.into()
})
).expect_or_log("Failed to connect to database");
RwLock::new(db_conn)
});

View file

@ -0,0 +1,7 @@
mod definition;
pub use definition::{
BaseTheme,
ThemeTrait,
};
pub(crate) mod all;

View file

@ -0,0 +1,42 @@
use crate::{Lazy, app, theme_static_files, trace};
use super::ThemeTrait;
use std::sync::RwLock;
include!(concat!(env!("OUT_DIR"), "/theme.rs"));
// Temas registrados.
static THEMES: Lazy<RwLock<Vec<&dyn ThemeTrait>>> = Lazy::new(|| {
RwLock::new(Vec::new())
});
pub fn register_themes(themes: Vec<&'static dyn ThemeTrait>) {
for t in themes {
register(t)
}
}
fn register(theme: &'static dyn ThemeTrait) {
let mut themes = THEMES.write().unwrap();
if !themes.iter().any(|t| t.handler() == theme.handler()) {
trace::debug!("Registering theme \"{}\"", theme.single_name());
themes.push(theme);
}
}
pub fn theme_by_single_name(single_name: &str) -> Option<&'static dyn ThemeTrait> {
match THEMES.write().unwrap().iter().find(
|t| t.single_name().to_lowercase() == single_name.to_lowercase()
) {
Some(theme) => Some(*theme),
_ => None,
}
}
pub fn themes(cfg: &mut app::web::ServiceConfig) {
theme_static_files!(cfg, "/theme");
for t in THEMES.read().unwrap().iter() {
t.configure_service(cfg);
}
}

View file

@ -1,57 +1,67 @@
use crate::{app, concat_string, util};
use crate::config::SETTINGS;
use crate::core::server;
use crate::core::theme::{Markup, html};
use crate::core::response::page::{Page, PageAssets, PageComponent};
use crate::html::{Favicon, Markup, html};
use crate::core::component::{ComponentTrait, InContext};
use crate::response::page::Page;
use crate::base::component::Chunck;
/// Los temas deben implementar este "trait".
pub trait Theme: Send + Sync {
fn name(&self) -> &'static str;
pub trait BaseTheme {
fn single_name(&self) -> &'static str;
}
fn fullname(&self) -> String;
/// Los temas deben implementar este "trait".
pub trait ThemeTrait: BaseTheme + Send + Sync {
fn handler(&self) -> &'static str;
fn name(&self) -> String {
self.single_name().to_owned()
}
fn description(&self) -> Option<String> {
None
}
#[allow(unused_variables)]
fn configure_theme(&self, cfg: &mut server::web::ServiceConfig) {
fn configure_service(&self, cfg: &mut app::web::ServiceConfig) {
}
#[allow(unused_variables)]
fn before_render_page(&self, page: &mut Page) {
page.context()
.with_favicon(Some(Favicon::new()
.with_icon("/theme/favicon.png")
));
}
fn render_page_head(&self, page: &mut Page) -> Markup {
let title = page.title();
let title = if title.is_empty() {
SETTINGS.app.name.to_owned()
} else {
[SETTINGS.app.name.to_string(), title.to_string()].join(" | ")
};
let description = page.description();
let viewport = "width=device-width, initial-scale=1, shrink-to-fit=no";
html! {
head {
meta charset="utf-8";
title { (title) }
@match page.title() {
Some(t) => title {
(concat_string!(SETTINGS.app.name, " | ", t))
},
None => title { (SETTINGS.app.name) }
}
@if !description.is_empty() {
meta name="description" content=(description);
@match page.description() {
Some(d) => meta name="description" content=(d);,
None => {}
}
meta http-equiv="X-UA-Compatible" content="IE=edge";
meta name="viewport" content=(viewport);
(page.assets().render())
(page.context().render())
}
}
}
fn render_page_body(&self, page: &mut Page) -> Markup {
html! {
body class=(page.body_classes()) {
body class=[page.body_classes()] {
@match page.template() {
"admin" => {
@for region in &["top-menu", "side-menu", "content"] {
@ -70,33 +80,52 @@ pub trait Theme: Send + Sync {
}
}
#[allow(unused_variables)]
fn before_render_component(
&self,
component: &mut dyn ComponentTrait,
context: &mut InContext
) {
/*
Cómo usarlo:
match component.handler() {
BLOCK_COMPONENT => {
let block = component_mut::<Block>(component);
block.alter_title("New title");
},
_ => {},
}
*/
}
#[allow(unused_variables)]
fn render_component(
&self,
component: &dyn PageComponent,
assets: &mut PageAssets
component: &dyn ComponentTrait,
context: &mut InContext
) -> Option<Markup> {
None
/*
Cómo usarlo:
match component.name() {
"block" => {
let block = component.downcast_mut::<Block>().unwrap();
match component.handler() {
BLOCK_COMPONENT => {
let block = component_ref::<Block>(component);
match block.template() {
"default" => Some(block_default(block)),
_ => None
_ => None,
}
},
_ => None
_ => None,
}
*/
}
fn render_error_page(&self, s: server::http::StatusCode) -> server::Result<Markup> {
Page::prepare()
fn render_error_page(&self, s: app::http::StatusCode) -> app::Result<Markup> {
Page::new()
.with_title(format!("Error {}", s.as_str()).as_str())
.add_to("content", Chunck::markup(html! {
.add_to("content", Chunck::with(html! {
div {
h1 { (s) }
}
@ -104,3 +133,9 @@ pub trait Theme: Send + Sync {
.render()
}
}
impl<T: ?Sized + ThemeTrait> BaseTheme for T {
fn single_name(&self) -> &'static str {
util::single_type_name::<Self>()
}
}

View file

@ -1,18 +0,0 @@
use crate::core::global;
pub use maud::{DOCTYPE, Markup, PreEscaped, html};
mod definition;
pub use definition::Theme;
pub fn register_theme(t: &'static (dyn Theme + 'static)) {
global::THEMES.write().unwrap().push(t);
}
pub fn find_theme(name: &str) -> Option<&'static (dyn Theme + 'static)> {
let themes = global::THEMES.write().unwrap();
match themes.iter().find(|t| t.name() == name) {
Some(theme) => Some(*theme),
_ => None,
}
}

View file

@ -1,25 +1,27 @@
pub use url::Url as DbUri;
pub use sea_orm::{
DbErr,
DatabaseConnection as DbConn,
};
pub use sea_orm::{DatabaseConnection as DbConn, ExecResult, QueryResult};
pub mod entity {
pub use sea_orm::entity::prelude::*;
}
pub use sea_orm_migration::prelude::*;
pub mod migration {
pub use sea_schema::migration::prelude::*;
pub type MigrationItem = Box<dyn MigrationTrait>;
#[macro_export]
macro_rules! pub_migration {
( $migration:ident ) => {
pub struct $migration;
impl MigrationName for $migration {
fn name(&self) -> &str {
crate::util::partial_type_name(module_path!(), 1)
}
}
};
}
#[macro_export]
macro_rules! db_migrations {
( $DBCONN:ident ) => {{
$crate::run_now({
use $crate::db::migration::MigratorTrait;
migration::Migrator::up($DBCONN, None)
})
macro_rules! migration_item {
( $migration_module:ident ) => {{
Box::new(migration::$migration_module::Migration)
}};
}

18
pagetop/src/html.rs Normal file
View file

@ -0,0 +1,18 @@
pub use maud::{DOCTYPE, Markup, PreEscaped, html};
mod assets;
pub use assets::Assets;
pub use assets::javascript::{JavaScript, JSMode};
pub use assets::stylesheet::StyleSheet;
mod favicon;
pub use favicon::Favicon;
mod optiden;
pub use optiden::OptIden;
mod optattr;
pub use optattr::OptAttr;
mod classes;
pub use classes::{Classes, ClassesOp};

View file

@ -0,0 +1,41 @@
pub mod javascript;
pub mod stylesheet;
use crate::html::{Markup, html};
pub trait AssetsTrait {
fn source(&self) -> &'static str;
fn weight(&self) -> isize;
fn render(&self) -> Markup;
}
pub struct Assets<T>(Vec<T>);
impl<T: AssetsTrait> Assets<T> {
pub fn new() -> Self {
Assets::<T>(Vec::<T>::new())
}
pub fn add(&mut self, assets: T) -> &mut Self {
match self.0.iter().position(|x| x.source() == assets.source()) {
Some(index) => if self.0[index].weight() > assets.weight() {
self.0.remove(index);
self.0.push(assets);
},
_ => self.0.push(assets)
}
self
}
pub fn render(&mut self) -> Markup {
let assets = &mut self.0;
assets.sort_by_key(|a| a.weight());
html! {
@for a in assets {
(a.render())
}
}
}
}

View file

@ -0,0 +1,51 @@
use crate::html::{Markup, html};
use super::AssetsTrait;
#[derive(PartialEq)]
pub enum JSMode { Async, Defer, Normal }
pub struct JavaScript {
source: &'static str,
weight: isize,
mode : JSMode,
}
impl AssetsTrait for JavaScript {
fn source(&self) -> &'static str {
self.source
}
fn weight(&self) -> isize {
self.weight
}
fn render(&self) -> Markup {
html! {
script type="text/javascript"
src=(self.source)
async[self.mode == JSMode::Async]
defer[self.mode == JSMode::Defer]
{};
}
}
}
impl JavaScript {
pub fn with_source(s: &'static str) -> Self {
JavaScript {
source: s,
weight: 0,
mode : JSMode::Defer,
}
}
pub fn with_weight(mut self, weight: isize) -> Self {
self.weight = weight;
self
}
pub fn with_mode(mut self, mode: JSMode) -> Self {
self.mode = mode;
self
}
}

View file

@ -0,0 +1,37 @@
use crate::html::{Markup, html};
use super::AssetsTrait;
pub struct StyleSheet {
source: &'static str,
weight: isize,
}
impl AssetsTrait for StyleSheet {
fn source(&self) -> &'static str {
self.source
}
fn weight(&self) -> isize {
self.weight
}
fn render(&self) -> Markup {
html! {
link rel="stylesheet" href=(self.source);
}
}
}
impl StyleSheet {
pub fn with_source(s: &'static str) -> Self {
StyleSheet {
source: s,
weight: 0,
}
}
pub fn with_weight(mut self, weight: isize) -> Self {
self.weight = weight;
self
}
}

View file

@ -0,0 +1,91 @@
use crate::concat_string;
pub enum ClassesOp {
Add,
AddAfter(&'static str),
AddBefore(&'static str),
AddFirst,
Replace(&'static str),
Reset,
SetDefault,
SetDefaultIfEmpty,
}
pub struct Classes {
default: String,
added : String,
option : Option<String>,
}
impl Classes {
pub fn new() -> Self {
Classes {
default: "".to_owned(),
added : "".to_owned(),
option : None,
}
}
pub fn new_with_default(default: &str) -> Self {
let mut classes = Self::new();
classes.alter(default, ClassesOp::SetDefault);
classes
}
pub fn alter(&mut self, classes: &str, op: ClassesOp) -> &Self {
let classes = classes.trim();
match op {
ClassesOp::Add => {
self.added = concat_string!(self.added, " ", classes).trim().to_owned()
},
ClassesOp::AddAfter(class) => {
let mut v_added: Vec<&str> = self.added.split_ascii_whitespace().collect();
match v_added.iter().position(|c| c.eq(&class)) {
Some(pos) => v_added.insert(pos + 1, classes),
_ => v_added.push(classes),
}
self.added = v_added.join(" ");
},
ClassesOp::AddBefore(class) => {
let mut v_added: Vec<&str> = self.added.split_ascii_whitespace().collect();
match v_added.iter().position(|c| c.eq(&class)) {
Some(pos) => v_added.insert(pos, classes),
_ => v_added.insert(0, classes),
}
self.added = v_added.join(" ");
},
ClassesOp::AddFirst => {
self.added = concat_string!(classes, " ", self.added).trim().to_owned()
},
ClassesOp::Replace(class) => {
let mut v_added: Vec<&str> = self.added.split_ascii_whitespace().collect();
match v_added.iter().position(|c| c.eq(&class)) {
Some(pos) => {
v_added.remove(pos);
v_added.insert(pos, classes);
},
_ => v_added.push(classes),
}
self.added = v_added.join(" ");
},
ClassesOp::Reset => self.added = classes.to_owned(),
ClassesOp::SetDefault => self.default = classes.to_owned(),
ClassesOp::SetDefaultIfEmpty => if self.default.is_empty() {
self.default = classes.to_owned()
},
}
self.option = Some(concat_string!(self.default, " ", self.added).trim().to_owned());
self
}
pub fn option(&self) -> &Option<String> {
&self.option
}
}

View file

@ -0,0 +1,86 @@
use crate::html::{Markup, PreEscaped, html};
pub struct Favicon(Vec<String>);
impl Favicon {
pub fn new() -> Self {
Favicon(Vec::new())
}
pub fn with_icon(self, image: &str) -> Self {
self.add_item("icon", image, "", "")
}
pub fn with_icon_for_sizes(self, image: &str, sizes: &str) -> Self {
self.add_item("icon", image, sizes, "")
}
pub fn with_apple_touch_icon(self, image: &str, sizes: &str) -> Self {
self.add_item("apple-touch-icon", image, sizes, "")
}
pub fn with_mask_icon(self, image: &str, color: &str) -> Self {
self.add_item("mask-icon", image, "", color)
}
pub fn with_manifest(self, file: &str) -> Self {
self.add_item("manifest", file, "", "")
}
pub fn with_theme_color(mut self, color: &str) -> Self {
self.0.push(format!(
"<meta name=\"theme-color\" content=\"{}\">", color
));
self
}
pub fn with_ms_tile_color(mut self, color: &str) -> Self {
self.0.push(format!(
"<meta name=\"msapplication-TileColor\" content=\"{}\">", color
));
self
}
pub fn with_ms_tile_image(mut self, image: &str) -> Self {
self.0.push(format!(
"<meta name=\"msapplication-TileImage\" content=\"{}\">", image
));
self
}
fn add_item(
mut self,
rel : &str,
source: &str,
sizes : &str,
color : &str
) -> Self {
let mut link: String = format!("<link rel=\"{}\"", rel);
if let Some(i) = source.rfind('.') {
link = match source[i..].to_owned().to_lowercase().as_str() {
".gif" => format!("{} type=\"image/gif\"", link),
".ico" => format!("{} type=\"image/x-icon\"", link),
".jpg" => format!("{} type=\"image/jpg\"", link),
".png" => format!("{} type=\"image/png\"", link),
".svg" => format!("{} type=\"image/svg+xml\"", link),
_ => link
};
}
if !sizes.is_empty() {
link = format!("{} sizes=\"{}\"", link, sizes);
}
if !color.is_empty() {
link = format!("{} color=\"{}\"", link, color);
}
self.0.push(format!("{} href=\"{}\">", link, source));
self
}
pub(crate) fn render(&self) -> Markup {
html! {
@for item in &self.0 {
(PreEscaped(item))
}
}
}
}

Some files were not shown because too many files have changed in this diff Show more