Integra la rama 'main' en 'actix-v4'
This commit is contained in:
commit
cafc4422fa
149 changed files with 7497 additions and 2100 deletions
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"] }
|
||||
14
pagetop/STARTER.lib.Cargo.toml
Normal file
14
pagetop/STARTER.lib.Cargo.toml
Normal 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"] }
|
||||
|
|
@ -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 = ""
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
72
pagetop/src/app/application.rs
Normal file
72
pagetop/src/app/application.rs
Normal 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
31
pagetop/src/app/banner.rs
Normal 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")
|
||||
);
|
||||
}
|
||||
}
|
||||
30
pagetop/src/app/banner/figfont.rs
Normal file
30
pagetop/src/app/banner/figfont.rs
Normal 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
89
pagetop/src/app/db.rs
Normal 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
|
||||
}
|
||||
22
pagetop/src/app/definition.rs
Normal file
22
pagetop/src/app/definition.rs
Normal 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![]
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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;
|
||||
44
pagetop/src/base/component.rs
Normal file
44
pagetop/src/base/component.rs
Normal 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
|
||||
};
|
||||
265
pagetop/src/base/component/anchor.rs
Normal file
265
pagetop/src/base/component/anchor.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
21
pagetop/src/base/component/form.rs
Normal file
21
pagetop/src/base/component/form.rs
Normal 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
|
||||
};
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
8
pagetop/src/base/component/grid.rs
Normal file
8
pagetop/src/base/component/grid.rs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
mod row;
|
||||
pub use row::{
|
||||
ROW_COMPONENT, Row
|
||||
};
|
||||
mod column;
|
||||
pub use column::{
|
||||
COLUMN_COMPONENT, Column
|
||||
};
|
||||
135
pagetop/src/base/component/grid/column.rs
Normal file
135
pagetop/src/base/component/grid/column.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
135
pagetop/src/base/component/grid/row.rs
Normal file
135
pagetop/src/base/component/grid/row.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
215
pagetop/src/base/component/heading.rs
Normal file
215
pagetop/src/base/component/heading.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
95
pagetop/src/base/component/icon.rs
Normal file
95
pagetop/src/base/component/icon.rs
Normal 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
|
||||
}
|
||||
}
|
||||
142
pagetop/src/base/component/image.rs
Normal file
142
pagetop/src/base/component/image.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
172
pagetop/src/base/component/paragraph.rs
Normal file
172
pagetop/src/base/component/paragraph.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
1
pagetop/src/base/module.rs
Normal file
1
pagetop/src/base/module.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
pub mod demopage;
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
module_fullname = Admin module
|
||||
module_description = Administration module.
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
module_fullname = Admin module
|
||||
module_description = Módulo de administración.
|
||||
|
|
@ -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))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
230
pagetop/src/base/module/demopage.rs
Normal file
230
pagetop/src/base/module/demopage.rs
Normal 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" {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
25
pagetop/src/base/module/demopage/locales/en-US/demopage.ftl
Normal file
25
pagetop/src/base/module/demopage/locales/en-US/demopage.ftl
Normal 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.
|
||||
25
pagetop/src/base/module/demopage/locales/es-ES/demopage.ftl
Normal file
25
pagetop/src/base/module/demopage/locales/es-ES/demopage.ftl
Normal 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í.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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í.
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
pub mod admin;
|
||||
pub mod homepage;
|
||||
|
||||
#[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))]
|
||||
pub mod user;
|
||||
|
|
@ -1 +0,0 @@
|
|||
pub mod user;
|
||||
|
|
@ -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 {}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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)]
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
pub mod aliner;
|
||||
pub mod minimal;
|
||||
pub mod bootsier;
|
||||
pub mod bulmix;
|
||||
30
pagetop/src/base/theme/aliner.rs
Normal file
30
pagetop/src/base/theme/aliner.rs
Normal 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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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!";
|
||||
78
pagetop/src/base/theme/bulmix.rs
Normal file
78
pagetop/src/base/theme/bulmix.rs
Normal 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);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
11
pagetop/src/base/theme/minimal.rs
Normal file
11
pagetop/src/base/theme/minimal.rs
Normal 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
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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
4
pagetop/src/core.rs
Normal 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.
|
||||
28
pagetop/src/core/component.rs
Normal file
28
pagetop/src/core/component.rs
Normal 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 }
|
||||
22
pagetop/src/core/component/all.rs
Normal file
22
pagetop/src/core/component/all.rs
Normal 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()
|
||||
}
|
||||
37
pagetop/src/core/component/bundle.rs
Normal file
37
pagetop/src/core/component/bundle.rs
Normal 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
118
pagetop/src/core/component/context.rs
Normal file
118
pagetop/src/core/component/context.rs
Normal 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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
66
pagetop/src/core/component/definition.rs
Normal file
66
pagetop/src/core/component/definition.rs
Normal 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! {}
|
||||
}
|
||||
}
|
||||
48
pagetop/src/core/component/hook.rs
Normal file
48
pagetop/src/core/component/hook.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
14
pagetop/src/core/hook.rs
Normal 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;
|
||||
26
pagetop/src/core/hook/all.rs
Normal file
26
pagetop/src/core/hook/all.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
17
pagetop/src/core/hook/definition.rs
Normal file
17
pagetop/src/core/hook/definition.rs
Normal 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()
|
||||
}
|
||||
36
pagetop/src/core/hook/holder.rs
Normal file
36
pagetop/src/core/hook/holder.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
pub use actix_web::dev::Server;
|
||||
|
||||
mod global;
|
||||
|
||||
pub mod theme;
|
||||
pub mod module;
|
||||
pub mod response;
|
||||
pub mod server;
|
||||
7
pagetop/src/core/module.rs
Normal file
7
pagetop/src/core/module.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
mod definition;
|
||||
pub use definition::{
|
||||
BaseModule,
|
||||
ModuleTrait,
|
||||
};
|
||||
|
||||
pub(crate) mod all;
|
||||
84
pagetop/src/core/module/all.rs
Normal file
84
pagetop/src/core/module/all.rs
Normal 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();
|
||||
}
|
||||
|
|
@ -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>()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
});
|
||||
7
pagetop/src/core/theme.rs
Normal file
7
pagetop/src/core/theme.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
mod definition;
|
||||
pub use definition::{
|
||||
BaseTheme,
|
||||
ThemeTrait,
|
||||
};
|
||||
|
||||
pub(crate) mod all;
|
||||
42
pagetop/src/core/theme/all.rs
Normal file
42
pagetop/src/core/theme/all.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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>()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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
18
pagetop/src/html.rs
Normal 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};
|
||||
41
pagetop/src/html/assets.rs
Normal file
41
pagetop/src/html/assets.rs
Normal 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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
51
pagetop/src/html/assets/javascript.rs
Normal file
51
pagetop/src/html/assets/javascript.rs
Normal 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
|
||||
}
|
||||
}
|
||||
37
pagetop/src/html/assets/stylesheet.rs
Normal file
37
pagetop/src/html/assets/stylesheet.rs
Normal 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
|
||||
}
|
||||
}
|
||||
91
pagetop/src/html/classes.rs
Normal file
91
pagetop/src/html/classes.rs
Normal 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
|
||||
}
|
||||
}
|
||||
86
pagetop/src/html/favicon.rs
Normal file
86
pagetop/src/html/favicon.rs
Normal 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
Loading…
Add table
Add a link
Reference in a new issue