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

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

View file

@ -1,9 +1,9 @@
# FIGfonts
PageTop utiliza el paquete [figlet-rs](https://crates.io/crates/figlet-rs) de
*yuanbohan*, que muestra al inicio de la ejecución un rótulo con el nombre de
*yuanbohan*. Muestra en el terminal un rótulo de presentación con el nombre de
la aplicación usando caracteres [FIGlet](http://www.figlet.org/). Las fuentes
incluidas en `resources` son:
incluidas en `src/app/banner` son:
* [slant.flf](http://www.figlet.org/fontdb_example.cgi?font=slant.flf)
por *Glenn Chappell*.
@ -13,3 +13,11 @@ incluidas en `resources` son:
por *Claude Martins*.
* [starwars.flf](http://www.figlet.org/fontdb_example.cgi?font=starwars.flf)
por *Ryan Youck*.
# Icono
El monstruo sonriente de Frankenstein es una divertida creación de
[Webalys](https://www.iconfinder.com/webalys). Puede encontrarse en su colección
[Nasty Icons](https://www.iconfinder.com/iconsets/nasty) disponible en
[ICONFINDER](https://www.iconfinder.com/).

View file

@ -2,4 +2,7 @@
members = [
"drust",
"pagetop",
"pagetop-admin",
"pagetop-user",
"pagetop-node",
]

201
LICENSE-APACHE Normal file
View file

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2022 Manuel Cillero
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

21
LICENSE-MIT Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Manuel Cillero
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

48
README.md Normal file
View file

@ -0,0 +1,48 @@
**PageTop** es un entorno de desarrollo basado en [Rust](https://www.rust-lang.org/es/) que reúne
algunos de los crates más estables y populares para crear soluciones web modulares, extensibles y
configurables.
Incluye **Drust**, un sistema de gestión de contenidos basado en PageTop que permite crear, editar y
mantener sitios web dinámicos, rápidos y seguros.
# Advertencia
**PageTop** es un proyecto personal para aprender Rust y conocer su ecosistema. Ahora mismo sólo se
liberan versiones de desarrollo. En este contexto la API no tiene ninguna estabilidad y los cambios
son constantes. No puede considerarse listo para probar hasta que se libere la versión **0.1.0**.
# Estructura del código
El repositorio se organiza en un *workspace* con los siguientes subproyectos:
* [pagetop](pagetop/), es la librería esencial construida con *crates* estables y muy conocidos del
ecosistema Rust para proporcionar APIs, patrones de desarrollo y buenas prácticas para la creación
avanzada de soluciones web SSR (*Server-Side Rendering*).
* [pagetop-admin](pagetop_admin/), módulo que proporciona a otros módulos un lugar común donde
presentar a los administradores sus opciones de configuración.
* [pagetop-user](pagetop_user/), módulo para añadir gestión de usuarios, roles, permisos y sesiones
en aplicaciones desarrolladas con PageTop.
* [pagetop-node](pagetop_node/), módulo para crear, extender o personalizar los tipos de contenido
que puede administrar un sitio web.
* [drust](drust/) es una aplicación inspirada modestamente en [Drupal](https://www.drupal.org) que
proporciona un CMS (*Content Management System*) o sistema de gestión de contenidos para construir
sitios web dinámicos, administrados y configurables.
# Licencia
Este proyecto tiene licencia, de hecho se puede aplicar cualquiera de las siguientes a tu elección:
* Licencia Apache versión 2.0
([LICENSE-APACHE](https://gitlab.com/manuelcillero/pagetop/-/blob/main/LICENSE-APACHE) o
[http://www.apache.org/licenses/LICENSE-2.0]).
* Licencia MIT
([LICENSE-MIT](https://gitlab.com/manuelcillero/pagetop/-/blob/main/LICENSE-MIT) o
[http://opensource.org/licenses/MIT]).

View file

@ -1,12 +1,13 @@
[app]
name = "Drust"
description = """\
A modern Content Management System for sharing the world.\
A modern web Content Management System to share your world.\
"""
language = "es-ES"
#theme = "Aliner"
#theme = "Minimal"
theme = "Bootsier"
#theme = "Bulmix"
language = "es-ES"
[database]
db_type = "mysql"
@ -15,4 +16,7 @@ db_user = "drust"
db_pass = "DrU__#3T"
[log]
tracing = "Info,sqlx::query=Warn"
tracing = "Info,pagetop=Debug,sqlx::query=Warn"
[dev]
#static_files = "pagetop/static"

View file

@ -7,10 +7,11 @@ authors = [
"Manuel Cillero <manuel@cillero.es>"
]
description = """\
A modern Content Management System for sharing the world.\
A modern web Content Management System to share your world.\
"""
homepage = "https://suitepro.cillero.es/projects/drust"
repository = "https://gitlab.com/manuelcillero/drust"
license = "Apache-2.0 or MIT"
[dependencies.pagetop]
path = "../pagetop"
@ -19,3 +20,6 @@ default-features = false
[dependencies]
actix-web = "3.3.3"
pagetop-admin = { path = "../pagetop-admin" }
pagetop-user = { path = "../pagetop-user" }
pagetop-node = { path = "../pagetop-node" }

View file

@ -1,4 +1,25 @@
use pagetop::prelude::*;
struct Drust;
impl AppTrait for Drust {
fn enable_modules(&self) -> Vec<&'static dyn ModuleTrait> {
vec![
&pagetop_admin::Admin,
&pagetop_user::User,
&pagetop_node::Node,
&pagetop::base::module::demopage::Demopage,
]
}
fn themes(&self) -> Vec<&'static dyn ThemeTrait> {
vec![
&pagetop::base::theme::bulmix::Bulmix,
]
}
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
pagetop::Application::build(None).await?.run()?.await
Application::prepare(Drust).await?.run()?.await
}

18
pagetop-admin/Cargo.toml Normal file
View file

@ -0,0 +1,18 @@
[package]
name = "pagetop-admin"
version = "0.0.1"
edition = "2021"
authors = [
"Manuel Cillero <manuel@cillero.es>"
]
description = """\
...\
"""
homepage = "https://suitepro.cillero.es/projects/pagetop"
repository = "https://gitlab.com/manuelcillero/pagetop"
license = "Apache-2.0 or MIT"
[dependencies]
pagetop = { path = "../pagetop" }
maud = { version = "0.23.0" }

40
pagetop-admin/src/lib.rs Normal file
View file

@ -0,0 +1,40 @@
use pagetop::prelude::*;
pub const ADMIN_MODULE: &str = "pagetop-admin::module::admin";
localize!("src/locales");
mod summary;
pub struct Admin;
impl ModuleTrait for Admin {
fn handler(&self) -> &'static str {
ADMIN_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.service(
app::web::scope("/admin")
.route("", app::web::get().to(summary::summary))
);
}
fn actions(&self) -> Vec<HookItem> {
vec![
hook_item!(BeforeRenderPageHook => before_render_page)
]
}
}
fn before_render_page(page: &mut Page) {
page.alter_body_classes("test-admin", ClassesOp::Add);
}

View file

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

View file

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

View file

@ -1,51 +1,51 @@
use crate::prelude::*;
use pagetop::prelude::*;
use super::l;
pub async fn summary() -> server::Result<Markup> {
let top_menu = Menu::prepare()
.add(MenuItem::label(l("module_fullname").as_str()))
pub async fn summary() -> app::Result<Markup> {
let top_menu = Menu::new()
.add(MenuItem::label(l("module_name").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::submenu("Submenú 1", Menu::new()
.add(MenuItem::label("Opción 1"))
.add(MenuItem::label("Opción 2"))
))
.add(MenuItem::separator())
.add(MenuItem::submenu("Submenú 2", Menu::prepare()
.add(MenuItem::submenu("Submenú 2", Menu::new()
.add(MenuItem::label("Opción 1"))
.add(MenuItem::label("Opción 2"))
))
.add(MenuItem::label("Opción 4"));
let side_menu = Menu::prepare()
let side_menu = Menu::new()
.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::submenu("Submenú 1", Menu::new()
.add(MenuItem::label("Opción 1"))
.add(MenuItem::label("Opción 2"))
))
.add(MenuItem::separator())
.add(MenuItem::submenu("Submenú 2", Menu::prepare()
.add(MenuItem::submenu("Submenú 2", Menu::new()
.add(MenuItem::label("Opción 1"))
.add(MenuItem::label("Opción 2"))
))
.add(MenuItem::label("Opción 4"));
Page::prepare()
Page::new()
.using_theme("bootsier")
.using_theme("Bootsier")
.with_title("Admin")
.add_to("top-menu", top_menu)
.add_to("content", Container::row()
.add(Container::column()
.add_to("content", grid::Row::new()
.add_column(grid::Column::new()
.add(side_menu)
)
.add(Container::column()
.add(Chunck::markup(html! {
.add_column(grid::Column::new()
.add(Chunck::with(html! {
p { "Columna 2"}
}))
)

17
pagetop-node/Cargo.toml Normal file
View file

@ -0,0 +1,17 @@
[package]
name = "pagetop-node"
version = "0.0.1"
edition = "2021"
authors = [
"Manuel Cillero <manuel@cillero.es>"
]
description = """\
Allows content to be submitted to the site and displayed on pages.\
"""
homepage = "https://suitepro.cillero.es/projects/pagetop"
repository = "https://gitlab.com/manuelcillero/pagetop"
license = "Apache-2.0 or MIT"
[dependencies]
pagetop = { path = "../pagetop" }

55
pagetop-node/src/lib.rs Normal file
View file

@ -0,0 +1,55 @@
use pagetop::prelude::*;
pub const NODE_MODULE: &str = "pagetop-node::module::node";
localize!("src/locales");
//mod entity;
mod migration;
pub struct Node;
impl ModuleTrait for Node {
fn handler(&self) -> &'static str {
NODE_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("/node", app::web::get().to(node));
}
fn actions(&self) -> Vec<HookItem> {
vec![
hook_item!(BeforeRenderPageHook => before_render_page, -1)
]
}
fn migrations(&self) -> Vec<MigrationItem> {
vec![
migration_item!(m20220316_000001_create_table_node_type),
migration_item!(m20220316_000002_create_table_node),
migration_item!(m20220316_000003_create_table_node_access),
migration_item!(m20220316_000004_create_table_node_revision),
]
}
}
async fn node() -> app::Result<Markup> {
Page::new()
.with_title(
"Nodo"
)
.render()
}
fn before_render_page(page: &mut Page) {
page.alter_body_classes("test-node", ClassesOp::Add);
}

View file

@ -0,0 +1,2 @@
module_name = Node
module_description = Allows content to be submitted to the site and displayed on pages.

View file

@ -0,0 +1,2 @@
module_name = Nodo
module_description = Permite enviar contenidos al sitio y mostrarlos en páginas.

View file

@ -0,0 +1,4 @@
pub mod m20220316_000001_create_table_node_type;
pub mod m20220316_000002_create_table_node;
pub mod m20220316_000003_create_table_node_access;
pub mod m20220316_000004_create_table_node_revision;

View file

@ -0,0 +1,84 @@
use pagetop::prelude::*;
#[derive(Iden)]
enum NodeType {
Table, // node_type: Stores information about all defined Node types.
Type, // The machine-readable name of this type.
Name, // The human-readable name of this type.
Description, // Descripción breve del tipo.
Help, // Help information shown to the user when creating a Node of this type.
HasTitle, // Boolean indicating whether this type uses the Node.Title field.
TitleLabel, // The label displayed for the title field on the edit form.
Custom, // A boolean indicating whether this type is defined by a module (FALSE) or
// by a user via Add content type (TRUE).
Locked, // A boolean indicating whether the administrator can change the machine
// name of this type.
Disabled, // A boolean indicating whether the node type is disabled.
OrigType, // The original machine-readable name of this node type, this may be
// different from the current type name if the locked field is 0.
}
pub_migration!(Migration);
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager.create_table(Table::create()
.table(NodeType::Table)
.if_not_exists()
.col(ColumnDef::new(NodeType::Type)
.integer()
.not_null()
.auto_increment()
.primary_key(),
)
.col(ColumnDef::new(NodeType::Name)
.string()
.not_null()
)
.col(ColumnDef::new(NodeType::Description)
.string()
.not_null()
)
.col(ColumnDef::new(NodeType::Help)
.string()
.not_null()
)
.col(ColumnDef::new(NodeType::HasTitle)
.string()
.not_null()
)
.col(ColumnDef::new(NodeType::TitleLabel)
.string()
.not_null()
)
.col(ColumnDef::new(NodeType::Custom)
.string()
.not_null()
)
.col(ColumnDef::new(NodeType::Locked)
.string()
.not_null()
)
.col(ColumnDef::new(NodeType::Disabled)
.string()
.not_null()
)
.col(ColumnDef::new(NodeType::OrigType)
.string()
.not_null()
)
.to_owned()
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager.drop_table(Table::drop()
.table(NodeType::Table)
.to_owned()
)
.await
}
}

View file

@ -0,0 +1,107 @@
use pagetop::prelude::*;
#[derive(Iden)]
enum Node {
Table, // node: The base table for nodes.
Nid, // The primary identifier for a node.
Vid, // The current NodeRevision.vid version identifier.
Type, // The NodeType.type of this node.
Language, // The {languages}.language of this node.
Title, // The title of this node, always treated as non-markup plain text.
Uid, // The User.uid that owns this node; initially, this is the user that
// created it.
Status, // Boolean indicating whether the node is published (visible to
// non-administrators).
Created, // The Unix timestamp when the node was created.
Changed, // The Unix timestamp when the node was most recently saved.
Comment, // Whether comments are allowed on this node: 0 = no, 1 = closed (read
// only), 2 = open (read/write).
Promote, // Boolean indicating whether the node should be displayed on the front
// page.
Sticky, // Boolean indicating whether the node should be displayed at the top of
// lists in which it appears.
Tnid, // The translation set id for this node, which equals the node id of the
// source post in each set.
Translate, // A boolean indicating whether this translation page needs to be updated.
}
pub_migration!(Migration);
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager.create_table(Table::create()
.table(Node::Table)
.if_not_exists()
.col(ColumnDef::new(Node::Nid)
.integer()
.not_null()
.auto_increment()
.primary_key(),
)
.col(ColumnDef::new(Node::Vid)
.string()
.not_null()
)
.col(ColumnDef::new(Node::Type)
.string()
.not_null()
)
.col(ColumnDef::new(Node::Language)
.string()
.not_null()
)
.col(ColumnDef::new(Node::Title)
.string()
.not_null()
)
.col(ColumnDef::new(Node::Uid)
.string()
.not_null()
)
.col(ColumnDef::new(Node::Status)
.string()
.not_null()
)
.col(ColumnDef::new(Node::Created)
.string()
.not_null()
)
.col(ColumnDef::new(Node::Changed)
.string()
.not_null()
)
.col(ColumnDef::new(Node::Comment)
.string()
.not_null()
)
.col(ColumnDef::new(Node::Promote)
.string()
.not_null()
)
.col(ColumnDef::new(Node::Sticky)
.string()
.not_null()
)
.col(ColumnDef::new(Node::Tnid)
.string()
.not_null()
)
.col(ColumnDef::new(Node::Translate)
.string()
.not_null()
)
.to_owned()
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager.drop_table(Table::drop()
.table(Node::Table)
.to_owned()
)
.await
}
}

View file

@ -0,0 +1,67 @@
use pagetop::prelude::*;
#[derive(Iden)]
enum NodeAccess {
Table, // node_access: Identifies which realm/grant pairs a user must possess in
// order to view, update, or delete specific nodes.
Nid, // The Node.nid this record affects.
Gid, // The grant ID a user must possess in the specified realm to gain this
// row's privileges on the node.
Realm, // The realm in which the user must possess the grant ID. Each node access
// node can define one or more realms.
GrantView, // Boolean indicating whether a user with the realm/grant pair can view this
// node.
GrantUpdate, // Boolean indicating whether a user with the realm/grant pair can edit this
// node.
GrantDelete, // Boolean indicating whether a user with the realm/grant pair can delete
// this node.
}
pub_migration!(Migration);
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager.create_table(Table::create()
.table(NodeAccess::Table)
.if_not_exists()
.col(ColumnDef::new(NodeAccess::Nid)
.integer()
.not_null()
.auto_increment()
.primary_key(),
)
.col(ColumnDef::new(NodeAccess::Gid)
.string()
.not_null()
)
.col(ColumnDef::new(NodeAccess::Realm)
.string()
.not_null()
)
.col(ColumnDef::new(NodeAccess::GrantView)
.string()
.not_null()
)
.col(ColumnDef::new(NodeAccess::GrantUpdate)
.string()
.not_null()
)
.col(ColumnDef::new(NodeAccess::GrantDelete)
.string()
.not_null()
)
.to_owned()
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager.drop_table(Table::drop()
.table(NodeAccess::Table)
.to_owned()
)
.await
}
}

View file

@ -0,0 +1,85 @@
use pagetop::prelude::*;
#[derive(Iden)]
enum NodeRevision {
Table, // node_revision: Stores information about each saved version of a Node.
Nid, // The Node this version belongs to.
Vid, // The primary identifier for this version.
Uid, // The User.uid that created this version.
Title, // The title of this version.
Log, // The log entry explaining the changes in this version.
Timestamp, // A Unix timestamp indicating when this version was created.
Status, // Boolean indicating whether the node (at the time of this revision) is
// published (visible to non-administrators).
Comment, // Whether comments are allowed on this node (at the time of this revision):
// 0 = no, 1 = closed (read only), 2 = open (read/write).
Promote, // Boolean indicating whether the node (at the time of this revision) should
// be displayed on the front page.
Sticky, // Boolean indicating whether the node (at the time of this revision) should
// be displayed at the top of lists in which it appears.
}
pub_migration!(Migration);
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager.create_table(Table::create()
.table(NodeRevision::Table)
.if_not_exists()
.col(ColumnDef::new(NodeRevision::Nid)
.integer()
.not_null()
.auto_increment()
.primary_key(),
)
.col(ColumnDef::new(NodeRevision::Vid)
.string()
.not_null()
)
.col(ColumnDef::new(NodeRevision::Uid)
.string()
.not_null()
)
.col(ColumnDef::new(NodeRevision::Title)
.string()
.not_null()
)
.col(ColumnDef::new(NodeRevision::Log)
.string()
.not_null()
)
.col(ColumnDef::new(NodeRevision::Timestamp)
.string()
.not_null()
)
.col(ColumnDef::new(NodeRevision::Status)
.string()
.not_null()
)
.col(ColumnDef::new(NodeRevision::Comment)
.string()
.not_null()
)
.col(ColumnDef::new(NodeRevision::Promote)
.string()
.not_null()
)
.col(ColumnDef::new(NodeRevision::Sticky)
.string()
.not_null()
)
.to_owned()
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager.drop_table(Table::drop()
.table(NodeRevision::Table)
.to_owned()
)
.await
}
}

18
pagetop-user/Cargo.toml Normal file
View file

@ -0,0 +1,18 @@
[package]
name = "pagetop-user"
version = "0.0.1"
edition = "2021"
authors = [
"Manuel Cillero <manuel@cillero.es>"
]
description = """\
Allows content to be submitted to the site and displayed on pages.\
"""
homepage = "https://suitepro.cillero.es/projects/pagetop"
repository = "https://gitlab.com/manuelcillero/pagetop"
license = "Apache-2.0 or MIT"
[dependencies]
pagetop = { path = "../pagetop" }
serde = { version = "1.0", features = ["derive"] }

67
pagetop-user/src/lib.rs Normal file
View file

@ -0,0 +1,67 @@
use pagetop::prelude::*;
pub const USER_MODULE: &str = "pagetop-user::module::user";
localize!("src/locales");
mod migration;
pub struct User;
impl ModuleTrait for User {
fn handler(&self) -> &'static str {
USER_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("/user/login", app::web::get().to(login));
}
fn migrations(&self) -> Vec<MigrationItem> {
vec![
migration_item!(m20220312_000001_create_table_role),
migration_item!(m20220312_000002_create_table_role_permission),
migration_item!(m20220312_000003_create_table_user),
migration_item!(m20220312_000004_create_table_user_role),
]
}
}
async fn login() -> app::Result<Markup> {
Page::new()
.with_title(
"Identificación del usuario"
)
.add_to("content", Container::new()
.with_id("welcome")
.add(form_login())
)
.render()
}
fn form_login() -> Form {
Form::new()
.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())
.with_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()))
}

View file

@ -1,4 +1,4 @@
module_fullname = User
module_name = User
module_description = Manages the user registration and login system.
username = User name

View file

@ -1,5 +1,5 @@
module_fullname = Usuario
module_description = Gestion el registro de usuarios y el sistema de accesos.
module_name = Usuario
module_description = Gestiona el registro de usuarios y el sistema de accesos.
username = Nombre de usuario
password = Contraseña

View file

@ -0,0 +1,4 @@
pub mod m20220312_000001_create_table_role;
pub mod m20220312_000002_create_table_role_permission;
pub mod m20220312_000003_create_table_user;
pub mod m20220312_000004_create_table_user_role;

View file

@ -0,0 +1,64 @@
use pagetop::prelude::*;
#[derive(Iden)]
enum Role {
Table, // role: Store user roles.
Rid, // Primary Key: Unique role ID.
Name, // Unique role name.
Weight, // The weight of this role in listings and the user interface.
}
pub_migration!(Migration);
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager.create_table(Table::create()
.table(Role::Table)
.if_not_exists()
.col(ColumnDef::new(Role::Rid)
.unsigned()
.not_null()
.auto_increment()
.primary_key()
)
.col(ColumnDef::new(Role::Name)
.string_len(64)
.not_null()
.unique_key()
)
.col(ColumnDef::new(Role::Weight)
.integer()
.not_null()
.default(10)
)
// INDEXES.
.index(Index::create()
.name("weight-name")
.col(Role::Weight)
.col(Role::Name)
)
.to_owned()
)
.await?;
// Built-in roles.
app::db::exec::<InsertStatement>(Query::insert()
.into_table(Role::Table)
.columns(vec![Role::Name, Role::Weight])
.values_panic(vec!["anonymous".into(), "1".into()])
.values_panic(vec!["authenticated".into(), "2".into()])
.values_panic(vec!["administrator".into(), "3".into()])
)
.await.map(|_| ())
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager.drop_table(Table::drop()
.table(Role::Table)
.to_owned()
)
.await
}
}

View file

@ -0,0 +1,58 @@
use pagetop::prelude::*;
#[derive(Iden)]
enum RolePermission {
Table, // role_permission: Stores the permissions assigned to user roles.
Rid, // Foreign Key: Role::Rid.
Permission, // A single permission granted to the role identified by Rid.
}
#[derive(Iden)]
enum Role { Table, Rid, /* ... */ }
pub_migration!(Migration);
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager.create_table(Table::create()
.table(RolePermission::Table)
.if_not_exists()
.col(ColumnDef::new(RolePermission::Rid)
.unsigned()
.not_null()
)
.col(ColumnDef::new(RolePermission::Permission)
.string_len(128)
.not_null()
)
// INDEXES.
.primary_key(Index::create()
.col(RolePermission::Rid)
.col(RolePermission::Permission)
)
.index(Index::create()
.name("permission")
.col(RolePermission::Permission)
)
.foreign_key(ForeignKey::create()
.name("fk_role_permission-rid")
.from(RolePermission::Table, RolePermission::Rid)
.to(Role::Table, Role::Rid)
.on_delete(ForeignKeyAction::Restrict)
.on_update(ForeignKeyAction::Restrict)
)
.to_owned()
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager.drop_table(Table::drop()
.table(RolePermission::Table)
.to_owned()
)
.await
}
}

View file

@ -0,0 +1,79 @@
use pagetop::prelude::*;
#[derive(Iden)]
enum User {
Table, // user: Stores user data.
Uid, // Primary Key: Unique user ID.
Name, // Unique user name.
Pass, // User's password (hashed).
Mail, // User's e-mail address.
Created, // Timestamp for when user was created.
Changed, // Timestamp for when user was changed.
Access, // Timestamp for previous time user accessed the site.
Login, // Timestamp for user's last login.
Status, // Whether the user is active(1) or blocked(0).
Timezone, // User's time zone.
}
pub_migration!(Migration);
#[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::Uid)
.unsigned()
.not_null()
.primary_key()
)
.col(ColumnDef::new(User::Name)
.string_len(60)
.not_null()
.unique_key()
)
.col(ColumnDef::new(User::Pass)
.string_len(128)
.not_null()
)
.col(ColumnDef::new(User::Mail)
.string_len(255)
)
.col(ColumnDef::new(User::Created)
.timestamp()
.not_null()
)
.col(ColumnDef::new(User::Changed)
.timestamp()
.not_null()
)
.col(ColumnDef::new(User::Access)
.timestamp()
.not_null()
)
.col(ColumnDef::new(User::Login)
.timestamp()
.not_null()
)
.col(ColumnDef::new(User::Status)
.boolean()
.not_null()
)
.col(ColumnDef::new(User::Timezone)
.string_len(32)
)
.to_owned()
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager.drop_table(Table::drop()
.table(User::Table)
.to_owned()
)
.await
}
}

View file

@ -0,0 +1,64 @@
use pagetop::prelude::*;
#[derive(Iden)]
enum UserRole {
Table, // user_role: Maps users to roles.
Uid, // Foreign Key: User::Uid for user.
Rid, // Foreign Key: Role::Rid for role.
}
#[derive(Iden)]
enum User { Table, Uid, /* ... */ }
#[derive(Iden)]
enum Role { Table, Rid, /* ... */ }
pub_migration!(Migration);
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager.create_table(Table::create()
.table(UserRole::Table)
.if_not_exists()
.col(ColumnDef::new(UserRole::Uid)
.unsigned()
.not_null()
)
.col(ColumnDef::new(UserRole::Rid)
.unsigned()
.not_null()
)
// INDEXES.
.primary_key(Index::create()
.col(UserRole::Uid)
.col(UserRole::Rid)
)
.foreign_key(ForeignKey::create()
.name("fk_user_role-uid")
.from(UserRole::Table, UserRole::Uid)
.to(User::Table, User::Uid)
.on_delete(ForeignKeyAction::Restrict)
.on_update(ForeignKeyAction::Restrict)
)
.foreign_key(ForeignKey::create()
.name("fk_user_role-rid")
.from(UserRole::Table, UserRole::Rid)
.to(Role::Table, Role::Rid)
.on_delete(ForeignKeyAction::Restrict)
.on_update(ForeignKeyAction::Restrict)
)
.to_owned()
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager.drop_table(Table::drop()
.table(UserRole::Table)
.to_owned()
)
.await
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

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

View file

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

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

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

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