Integra la rama 'main' en 'actix-v4'
This commit is contained in:
commit
cafc4422fa
149 changed files with 7497 additions and 2100 deletions
12
CREDITS.md
12
CREDITS.md
|
|
@ -1,9 +1,9 @@
|
|||
# FIGfonts
|
||||
|
||||
PageTop utiliza el paquete [figlet-rs](https://crates.io/crates/figlet-rs) de
|
||||
*yuanbohan*, que muestra al inicio de la ejecución un rótulo con el nombre de
|
||||
*yuanbohan*. Muestra en el terminal un rótulo de presentación con el nombre de
|
||||
la aplicación usando caracteres [FIGlet](http://www.figlet.org/). Las fuentes
|
||||
incluidas en `resources` son:
|
||||
incluidas en `src/app/banner` son:
|
||||
|
||||
* [slant.flf](http://www.figlet.org/fontdb_example.cgi?font=slant.flf)
|
||||
por *Glenn Chappell*.
|
||||
|
|
@ -13,3 +13,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/).
|
||||
|
|
@ -2,4 +2,7 @@
|
|||
members = [
|
||||
"drust",
|
||||
"pagetop",
|
||||
"pagetop-admin",
|
||||
"pagetop-user",
|
||||
"pagetop-node",
|
||||
]
|
||||
201
LICENSE-APACHE
Normal file
201
LICENSE-APACHE
Normal 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
21
LICENSE-MIT
Normal 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
48
README.md
Normal 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]).
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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" }
|
||||
|
|
|
|||
|
|
@ -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
18
pagetop-admin/Cargo.toml
Normal 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
40
pagetop-admin/src/lib.rs
Normal 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);
|
||||
}
|
||||
|
|
@ -1,2 +1,2 @@
|
|||
module_fullname = Admin module
|
||||
module_name = Admin module
|
||||
module_description = Administration module.
|
||||
|
|
@ -1,2 +1,2 @@
|
|||
module_fullname = Admin module
|
||||
module_name = Admin module
|
||||
module_description = Módulo de administración.
|
||||
|
|
@ -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
17
pagetop-node/Cargo.toml
Normal 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
55
pagetop-node/src/lib.rs
Normal 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);
|
||||
}
|
||||
2
pagetop-node/src/locales/en-US/homepage.ftl
Normal file
2
pagetop-node/src/locales/en-US/homepage.ftl
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
module_name = Node
|
||||
module_description = Allows content to be submitted to the site and displayed on pages.
|
||||
2
pagetop-node/src/locales/es-ES/homepage.ftl
Normal file
2
pagetop-node/src/locales/es-ES/homepage.ftl
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
module_name = Nodo
|
||||
module_description = Permite enviar contenidos al sitio y mostrarlos en páginas.
|
||||
4
pagetop-node/src/migration.rs
Normal file
4
pagetop-node/src/migration.rs
Normal 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;
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
107
pagetop-node/src/migration/m20220316_000002_create_table_node.rs
Normal file
107
pagetop-node/src/migration/m20220316_000002_create_table_node.rs
Normal 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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
18
pagetop-user/Cargo.toml
Normal 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
67
pagetop-user/src/lib.rs
Normal 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()))
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
module_fullname = User
|
||||
module_name = User
|
||||
module_description = Manages the user registration and login system.
|
||||
|
||||
username = User name
|
||||
|
|
@ -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
|
||||
4
pagetop-user/src/migration.rs
Normal file
4
pagetop-user/src/migration.rs
Normal 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;
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +1,18 @@
|
|||
[package]
|
||||
name = "pagetop"
|
||||
version = "0.0.1"
|
||||
version = "0.0.16"
|
||||
edition = "2021"
|
||||
|
||||
authors = [
|
||||
"Manuel Cillero <manuel@cillero.es>"
|
||||
]
|
||||
description = """\
|
||||
PageTop es un proyecto personal para aprender Rust. Incluye algunos de los \
|
||||
crates más estables y populares para desarrollar soluciones web modulares, \
|
||||
extensibles y configurables. También es un sistema para la gestión de \
|
||||
contenidos web.\
|
||||
PageTop is an opinionated framework for using the most stable and popular \
|
||||
Rust packages to build modular, extensible and configurable web solutions.\
|
||||
"""
|
||||
homepage = "https://suitepro.cillero.es/projects/pagetop"
|
||||
repository = "https://gitlab.com/manuelcillero/pagetop"
|
||||
license = "MIT"
|
||||
license = "Apache-2.0 or MIT"
|
||||
|
||||
keywords = [
|
||||
"web", "cms", "framework", "frontend", "ssr"
|
||||
|
|
@ -24,53 +22,56 @@ categories = [
|
|||
]
|
||||
|
||||
[dependencies]
|
||||
doc-comment = "0.3.3"
|
||||
downcast-rs = "1.2.0"
|
||||
figlet-rs = "0.1.3"
|
||||
futures = "0.3.21"
|
||||
once_cell = "1.9.0"
|
||||
url = "2.2.2"
|
||||
concat-string = "1.0.1"
|
||||
doc-comment = "0.3.3"
|
||||
figlet-rs = "0.1.3"
|
||||
futures = "0.3.21"
|
||||
once_cell = "1.12.0"
|
||||
substring = "1.4.5"
|
||||
term_size = "0.3.2"
|
||||
url = "2.2.2"
|
||||
|
||||
config_rs = { package = "config", version = "0.11.0", features = ["toml"] }
|
||||
|
||||
tracing = "0.1.32"
|
||||
tracing-appender = "0.2.1"
|
||||
tracing-subscriber = { version = "0.3.9", features = ["json", "env-filter"] }
|
||||
tracing = "0.1.35"
|
||||
tracing-appender = "0.2.2"
|
||||
tracing-subscriber = { version = "0.3.11", features = ["json", "env-filter"] }
|
||||
tracing-unwrap = { version = "0.9.2", default-features = false }
|
||||
tracing-actix-web = "0.5.1"
|
||||
tracing-actix-web = "0.6.0"
|
||||
|
||||
fluent-templates = "0.6.1"
|
||||
fluent-templates = "0.7.1"
|
||||
unic-langid = "0.9.0"
|
||||
|
||||
actix-web = "4.0.1"
|
||||
actix-web = "4.1.0"
|
||||
actix-web-static-files = "4.0.0"
|
||||
static-files = "0.2.3"
|
||||
actix-files = "0.6.1"
|
||||
|
||||
maud = { version = "0.23.0", features = ["actix-web"] }
|
||||
maud = { git = "https://github.com/lambda-fairy/maud", rev = "e6787cd62165a075c7f16a32f8bbacc398f52d13", features = ["actix-web"] }
|
||||
sycamore = { version = "0.7.1", features = ["ssr"] }
|
||||
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
||||
[dependencies.sea-orm]
|
||||
version = "0.6.0"
|
||||
version = "0.8.0"
|
||||
features = ["debug-print", "macros", "runtime-async-std-native-tls"]
|
||||
default-features = false
|
||||
optional = true
|
||||
|
||||
[dependencies.sea-schema]
|
||||
version = "0.6.0"
|
||||
features = ["debug-print", "migration"]
|
||||
default-features = false
|
||||
[dependencies.sea-orm-migration]
|
||||
version = "0.8.3"
|
||||
optional = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
mysql = ["sea-orm", "sea-schema", "sea-orm/sqlx-mysql"]
|
||||
postgres = ["sea-orm", "sea-schema", "sea-orm/sqlx-postgres"]
|
||||
sqlite = ["sea-orm", "sea-schema", "sea-orm/sqlx-sqlite"]
|
||||
mysql = ["sea-orm", "sea-orm-migration", "sea-orm/sqlx-mysql"]
|
||||
postgres = ["sea-orm", "sea-orm-migration", "sea-orm/sqlx-postgres"]
|
||||
sqlite = ["sea-orm", "sea-orm-migration", "sea-orm/sqlx-sqlite"]
|
||||
|
||||
[build-dependencies]
|
||||
static-files = "0.2.3"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.17.0", features = ["macros", "rt-multi-thread"] }
|
||||
|
||||
[lib]
|
||||
name = "pagetop"
|
||||
|
|
|
|||
|
|
@ -8,19 +8,18 @@ edition = "2021"
|
|||
|
||||
[dependencies.pagetop]
|
||||
path = "../pagetop"
|
||||
# Opcional. Por defecto se puede usar PageTop sin base de datos.
|
||||
# Si se usa base de datos:
|
||||
features = ["mysql"]
|
||||
# features = ["postgres"]
|
||||
# features = ["sqlite"]
|
||||
# PageTop puede dar soporte a todas las bases de datos.
|
||||
# Soporte alternativo a todas las bases de datos:
|
||||
# features = ["mysql", "postgres", "sqlite"]
|
||||
# Sólo cuando no se usen las características predeterminadas.
|
||||
# En estos casos hay que deshabilitar las características predeterminadas:
|
||||
default-features = false
|
||||
|
||||
[dependencies]
|
||||
# Requerido.
|
||||
actix-web = "3.3.3"
|
||||
# Opcional. Sólo si se usa la macro html!.
|
||||
# Si se usa la macro html!:
|
||||
maud = { version = "0.23.0" }
|
||||
# Opcional. Si se requiere serialización de estructuras de datos.
|
||||
# Si se requiere serialización de estructuras de datos:
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
14
pagetop/STARTER.lib.Cargo.toml
Normal file
14
pagetop/STARTER.lib.Cargo.toml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "app"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# Ver más claves y sus definiciones en
|
||||
# https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
pagetop = { path = "../pagetop" }
|
||||
# Si se usa la macro html!:
|
||||
maud = { version = "0.23.0" }
|
||||
# Si se requiere serialización de estructuras de datos:
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
|
@ -8,7 +8,7 @@ language = "en-US"
|
|||
# Dirección predeterminada para el texto: "ltr", "rtl" o "auto".
|
||||
direction = "ltr"
|
||||
# Rótulo al inicio: "Off", "Slant", "Small", "Speed" o "Starwars".
|
||||
startup_banner = "Small"
|
||||
startup_banner = "Slant"
|
||||
|
||||
[log]
|
||||
# Traza de ejecución: "Error", "Warn", "Info", "Debug" o "Trace".
|
||||
|
|
@ -25,7 +25,7 @@ format = "Full"
|
|||
|
||||
[database]
|
||||
# Conecta con una base de datos (opcional).
|
||||
# Tipo de la base de datos (mysql, postgres ó sqlite).
|
||||
# Tipo de base de datos (mysql, postgres ó sqlite).
|
||||
db_type = ""
|
||||
# Nombre (para mysql/postgres) o referencia (para sqlite) de la base de datos.
|
||||
db_name = ""
|
||||
|
|
@ -43,3 +43,11 @@ max_pool_size = 5
|
|||
# Configuración del servidor web.
|
||||
bind_address = "localhost"
|
||||
bind_port = 8088
|
||||
|
||||
[dev]
|
||||
# Los archivos estáticos requeridos por temas y componentes incluidos en PageTop
|
||||
# se integran de manera predeterminada en el binario ejecutable. Sin embargo, es
|
||||
# útil servir estos archivos desde su propio directorio durante el desarrollo ya
|
||||
# que no requiere compilar cada vez que se modifican. En este caso, normalmente,
|
||||
# basta con indicar el directorio "pagetop/static".
|
||||
static_files = ""
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ pub use actix_web::{
|
|||
App, HttpRequest, HttpResponse, HttpServer, Responder, Result, http, web
|
||||
};
|
||||
|
||||
mod banner;
|
||||
|
||||
mod tracing;
|
||||
|
||||
pub mod locale;
|
||||
|
|
@ -9,4 +11,7 @@ pub mod locale;
|
|||
#[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))]
|
||||
pub mod db;
|
||||
|
||||
pub mod app;
|
||||
mod definition;
|
||||
pub use definition::AppTrait;
|
||||
|
||||
pub mod application;
|
||||
72
pagetop/src/app/application.rs
Normal file
72
pagetop/src/app/application.rs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
use crate::{Lazy, base, trace};
|
||||
use crate::config::SETTINGS;
|
||||
use crate::core::{module, theme};
|
||||
use super::AppTrait;
|
||||
|
||||
use std::io::Error;
|
||||
use actix_web::middleware::normalize::{NormalizePath, TrailingSlash};
|
||||
use actix_web::dev::Server;
|
||||
|
||||
pub struct Application {
|
||||
server: Server,
|
||||
}
|
||||
|
||||
impl Application {
|
||||
pub async fn prepare(app: impl AppTrait) -> Result<Self, Error> {
|
||||
// Rótulo de presentación.
|
||||
super::banner::print_on_startup();
|
||||
|
||||
// Inicia registro de trazas y eventos.
|
||||
Lazy::force(&super::tracing::TRACING);
|
||||
|
||||
// Valida el identificador de idioma.
|
||||
Lazy::force(&super::locale::LANGID);
|
||||
|
||||
// Conecta con la base de datos (opcional).
|
||||
#[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))]
|
||||
Lazy::force(&super::db::DBCONN);
|
||||
|
||||
// Habilita los módulos de la aplicación.
|
||||
module::all::enable_modules(app.enable_modules());
|
||||
|
||||
// Registra los temas predeterminados.
|
||||
theme::all::register_themes(vec![
|
||||
&base::theme::aliner::Aliner,
|
||||
&base::theme::minimal::Minimal,
|
||||
&base::theme::bootsier::Bootsier,
|
||||
]);
|
||||
// Registra los temas de la aplicación.
|
||||
theme::all::register_themes(app.themes());
|
||||
|
||||
// Registra las acciones de todos los módulos.
|
||||
module::all::register_hooks();
|
||||
|
||||
// Ejecuta actualizaciones pendientes de la base de datos (opcional).
|
||||
#[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))]
|
||||
module::all::run_migrations();
|
||||
|
||||
// Ejecuta la función de inicio de la aplicación.
|
||||
trace::info!("Calling application bootstrap");
|
||||
app.bootstrap();
|
||||
|
||||
// Prepara el servidor web.
|
||||
let server = super::HttpServer::new(move || {
|
||||
super::App::new()
|
||||
.wrap(tracing_actix_web::TracingLogger)
|
||||
.wrap(NormalizePath::new(TrailingSlash::Trim))
|
||||
.configure(&module::all::modules)
|
||||
.configure(&theme::all::themes)
|
||||
})
|
||||
.bind(format!("{}:{}",
|
||||
&SETTINGS.webserver.bind_address,
|
||||
&SETTINGS.webserver.bind_port
|
||||
))?
|
||||
.run();
|
||||
|
||||
Ok(Self { server })
|
||||
}
|
||||
|
||||
pub fn run(self) -> Result<Server, Error> {
|
||||
Ok(self.server)
|
||||
}
|
||||
}
|
||||
31
pagetop/src/app/banner.rs
Normal file
31
pagetop/src/app/banner.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
mod figfont;
|
||||
use figfont::FIGFONT;
|
||||
|
||||
use crate::config::SETTINGS;
|
||||
|
||||
use substring::Substring;
|
||||
|
||||
pub fn print_on_startup() {
|
||||
if SETTINGS.app.startup_banner.to_lowercase() != "off" {
|
||||
if let Some((term_width, _)) = term_size::dimensions() {
|
||||
if term_width >= 80 {
|
||||
let maxlen = (term_width / 10) - 2;
|
||||
let mut app = SETTINGS.app.name.substring(0, maxlen).to_owned();
|
||||
if SETTINGS.app.name.len() > maxlen {
|
||||
app = format!("{}...", app);
|
||||
}
|
||||
println!("\n{} {}\n\n Powered by PageTop {}\n",
|
||||
FIGFONT.convert(&app).unwrap(),
|
||||
&SETTINGS.app.description,
|
||||
env!("CARGO_PKG_VERSION")
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
println!("\n{}\n{}\n\nPowered by PageTop {}\n",
|
||||
&SETTINGS.app.name,
|
||||
&SETTINGS.app.description,
|
||||
env!("CARGO_PKG_VERSION")
|
||||
);
|
||||
}
|
||||
}
|
||||
30
pagetop/src/app/banner/figfont.rs
Normal file
30
pagetop/src/app/banner/figfont.rs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
use crate::Lazy;
|
||||
use crate::config::SETTINGS;
|
||||
|
||||
use figlet_rs::FIGfont;
|
||||
|
||||
pub static FIGFONT: Lazy<FIGfont> = Lazy::new(|| {
|
||||
let slant = include_str!("slant.flf");
|
||||
let small = include_str!("small.flf");
|
||||
let speed = include_str!("speed.flf");
|
||||
let starwars = include_str!("starwars.flf");
|
||||
|
||||
FIGfont::from_content(
|
||||
match SETTINGS.app.startup_banner.to_lowercase().as_str() {
|
||||
"off" => slant,
|
||||
"slant" => slant,
|
||||
"small" => small,
|
||||
"speed" => speed,
|
||||
"starwars" => starwars,
|
||||
_ => {
|
||||
println!(
|
||||
"\n FIGfont \"{}\" not found for banner. {}. {}.",
|
||||
SETTINGS.app.startup_banner,
|
||||
"Using \"Slant\"",
|
||||
"Check the settings file",
|
||||
);
|
||||
slant
|
||||
}
|
||||
}
|
||||
).unwrap()
|
||||
});
|
||||
89
pagetop/src/app/db.rs
Normal file
89
pagetop/src/app/db.rs
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
use crate::{Lazy, run_now, trace};
|
||||
use crate::config::SETTINGS;
|
||||
use crate::db::*;
|
||||
|
||||
use sea_orm::{ConnectionTrait, ConnectOptions, Database, DatabaseBackend, Statement};
|
||||
use tracing_unwrap::ResultExt;
|
||||
|
||||
pub static DBCONN: Lazy<DbConn> = Lazy::new(|| {
|
||||
trace::info!(
|
||||
"Connecting to database \"{}\" using a pool of {} connections",
|
||||
&SETTINGS.database.db_name,
|
||||
&SETTINGS.database.max_pool_size
|
||||
);
|
||||
|
||||
let db_uri = match SETTINGS.database.db_type.as_str() {
|
||||
"mysql" | "postgres" => {
|
||||
let mut tmp_uri = DbUri::parse(format!(
|
||||
"{}://{}/{}",
|
||||
&SETTINGS.database.db_type,
|
||||
&SETTINGS.database.db_host,
|
||||
&SETTINGS.database.db_name
|
||||
).as_str()).unwrap();
|
||||
tmp_uri.set_username(
|
||||
&SETTINGS.database.db_user.as_str()
|
||||
).unwrap();
|
||||
// https://github.com/launchbadge/sqlx/issues/1624
|
||||
tmp_uri.set_password(
|
||||
Some(&SETTINGS.database.db_pass.as_str())
|
||||
).unwrap();
|
||||
if SETTINGS.database.db_port != 0 {
|
||||
tmp_uri.set_port(
|
||||
Some(SETTINGS.database.db_port)
|
||||
).unwrap();
|
||||
}
|
||||
tmp_uri
|
||||
},
|
||||
"sqlite" => DbUri::parse(
|
||||
format!("{}://{}",
|
||||
&SETTINGS.database.db_type,
|
||||
&SETTINGS.database.db_name
|
||||
).as_str()).unwrap(),
|
||||
_ => {
|
||||
trace::error!(
|
||||
"Unrecognized database type \"{}\"",
|
||||
&SETTINGS.database.db_type
|
||||
);
|
||||
DbUri::parse("").unwrap()
|
||||
}
|
||||
};
|
||||
|
||||
run_now(
|
||||
Database::connect::<ConnectOptions>({
|
||||
let mut db_opt = ConnectOptions::new(db_uri.to_string());
|
||||
db_opt.max_connections(SETTINGS.database.max_pool_size);
|
||||
db_opt.into()
|
||||
})
|
||||
).expect_or_log("Failed to connect to database")
|
||||
});
|
||||
|
||||
static DBBACKEND: Lazy<DatabaseBackend> = Lazy::new(|| {
|
||||
DBCONN.get_database_backend()
|
||||
});
|
||||
|
||||
|
||||
pub async fn query<Q: QueryStatementWriter>(stmt: &mut Q) -> Result<Vec<QueryResult>, DbErr> {
|
||||
DBCONN.query_all(Statement::from_string(
|
||||
*DBBACKEND,
|
||||
match *DBBACKEND {
|
||||
DatabaseBackend::MySql => stmt.to_string(MysqlQueryBuilder),
|
||||
DatabaseBackend::Postgres => stmt.to_string(PostgresQueryBuilder),
|
||||
DatabaseBackend::Sqlite => stmt.to_string(SqliteQueryBuilder),
|
||||
}
|
||||
)).await
|
||||
}
|
||||
|
||||
pub async fn exec<Q: QueryStatementWriter>(stmt: &mut Q) -> Result<Option<QueryResult>, DbErr> {
|
||||
DBCONN.query_one(Statement::from_string(
|
||||
*DBBACKEND,
|
||||
match *DBBACKEND {
|
||||
DatabaseBackend::MySql => stmt.to_string(MysqlQueryBuilder),
|
||||
DatabaseBackend::Postgres => stmt.to_string(PostgresQueryBuilder),
|
||||
DatabaseBackend::Sqlite => stmt.to_string(SqliteQueryBuilder),
|
||||
}
|
||||
)).await
|
||||
}
|
||||
|
||||
pub async fn exec_raw(stmt: String) -> Result<ExecResult, DbErr> {
|
||||
DBCONN.execute(Statement::from_string(*DBBACKEND, stmt)).await
|
||||
}
|
||||
22
pagetop/src/app/definition.rs
Normal file
22
pagetop/src/app/definition.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
use crate::base::module::demopage;
|
||||
use crate::core::module::ModuleTrait;
|
||||
use crate::core::theme::ThemeTrait;
|
||||
|
||||
pub trait AppTrait: Send + Sync {
|
||||
fn bootstrap(&self) {
|
||||
}
|
||||
|
||||
fn enable_modules(&self) -> Vec<&'static dyn ModuleTrait> {
|
||||
vec![
|
||||
&demopage::Demopage,
|
||||
]
|
||||
}
|
||||
|
||||
fn disable_modules(&self) -> Vec<&'static dyn ModuleTrait> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn themes(&self) -> Vec<&'static dyn ThemeTrait> {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
|
@ -11,11 +11,12 @@ pub static LANGID: Lazy<LanguageIdentifier> = Lazy::new(|| {
|
|||
Ok(language) => language,
|
||||
Err(_) => {
|
||||
trace::warn!(
|
||||
"Failed to parse language \"{}\". {}. {}. {}.",
|
||||
"{}, {} \"{}\"! {}, {}",
|
||||
"Failed to parse language",
|
||||
"unrecognized Unicode Language Identifier",
|
||||
SETTINGS.app.language,
|
||||
"Unrecognized Unicode Language Identifier",
|
||||
"Using \"en-US\"",
|
||||
"Check the settings file",
|
||||
"check the settings file",
|
||||
);
|
||||
"en-US".parse().unwrap()
|
||||
}
|
||||
|
|
@ -1,5 +1,3 @@
|
|||
//! Temas, Módulos y Componentes base.
|
||||
|
||||
pub mod theme;
|
||||
pub mod module;
|
||||
pub mod component;
|
||||
pub mod module;
|
||||
pub mod theme;
|
||||
44
pagetop/src/base/component.rs
Normal file
44
pagetop/src/base/component.rs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
mod container;
|
||||
pub use container::{
|
||||
CONTAINER_COMPONENT, Container, ContainerType
|
||||
};
|
||||
|
||||
pub mod grid;
|
||||
|
||||
mod chunck;
|
||||
pub use chunck::{
|
||||
CHUNCK_COMPONENT, Chunck
|
||||
};
|
||||
mod icon;
|
||||
pub use icon::{
|
||||
ICON_COMPONENT, Icon
|
||||
};
|
||||
mod heading;
|
||||
pub use heading::{
|
||||
HEADING_COMPONENT, Heading, HeadingDisplay, HeadingType
|
||||
};
|
||||
mod paragraph;
|
||||
pub use paragraph::{
|
||||
PARAGRAPH_COMPONENT, Paragraph, ParagraphDisplay
|
||||
};
|
||||
mod anchor;
|
||||
pub use anchor::{
|
||||
ANCHOR_COMPONENT, Anchor, AnchorIcon, AnchorTarget, AnchorType
|
||||
};
|
||||
mod block;
|
||||
pub use block::{
|
||||
BLOCK_COMPONENT, Block
|
||||
};
|
||||
mod image;
|
||||
pub use image::{
|
||||
IMAGE_COMPONENT, Image
|
||||
};
|
||||
mod menu;
|
||||
pub use menu::{
|
||||
MENU_COMPONENT, MENUITEM_COMPONENT, Menu, MenuItem, MenuItemType
|
||||
};
|
||||
|
||||
pub mod form;
|
||||
pub use form::{
|
||||
FORM_COMPONENT, Form, FormMethod
|
||||
};
|
||||
265
pagetop/src/base/component/anchor.rs
Normal file
265
pagetop/src/base/component/anchor.rs
Normal file
|
|
@ -0,0 +1,265 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
pub const ANCHOR_COMPONENT: &str = "pagetop::component::anchor";
|
||||
|
||||
pub enum AnchorType {
|
||||
Button,
|
||||
Link,
|
||||
Location,
|
||||
}
|
||||
|
||||
pub enum AnchorTarget {
|
||||
Blank,
|
||||
Context(String),
|
||||
Default,
|
||||
Parent,
|
||||
Top,
|
||||
}
|
||||
|
||||
pub type AnchorIcon = ComponentsBundle;
|
||||
|
||||
pub struct Anchor {
|
||||
renderable : fn() -> bool,
|
||||
weight : isize,
|
||||
anchor_type: AnchorType,
|
||||
href : OptAttr,
|
||||
html : Markup,
|
||||
left_icon : AnchorIcon,
|
||||
right_icon : AnchorIcon,
|
||||
target : AnchorTarget,
|
||||
id : OptIden,
|
||||
classes : Classes,
|
||||
template : String,
|
||||
}
|
||||
|
||||
impl ComponentTrait for Anchor {
|
||||
fn new() -> Self {
|
||||
Anchor {
|
||||
renderable : render_always,
|
||||
weight : 0,
|
||||
anchor_type: AnchorType::Link,
|
||||
href : OptAttr::new(),
|
||||
html : html! {},
|
||||
left_icon : AnchorIcon::new(),
|
||||
right_icon : AnchorIcon::new(),
|
||||
target : AnchorTarget::Default,
|
||||
id : OptIden::new(),
|
||||
classes : Classes::new(),
|
||||
template : "default".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
fn handler(&self) -> &'static str {
|
||||
ANCHOR_COMPONENT
|
||||
}
|
||||
|
||||
fn is_renderable(&self) -> bool {
|
||||
(self.renderable)()
|
||||
}
|
||||
|
||||
fn weight(&self) -> isize {
|
||||
self.weight
|
||||
}
|
||||
|
||||
fn default_render(&self, context: &mut InContext) -> Markup {
|
||||
let target = match &self.target() {
|
||||
AnchorTarget::Blank => Some("_blank"),
|
||||
AnchorTarget::Context(name) => Some(name.as_str()),
|
||||
AnchorTarget::Parent => Some("_parent"),
|
||||
AnchorTarget::Top => Some("_top"),
|
||||
_ => None,
|
||||
};
|
||||
html! {
|
||||
a
|
||||
id=[self.id()]
|
||||
class=[self.classes()]
|
||||
href=[self.href()]
|
||||
target=[target]
|
||||
{
|
||||
(self.left_icon().render(context))
|
||||
(" ")(*self.html())(" ")
|
||||
(self.right_icon().render(context))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn as_ref_any(&self) -> &dyn AnyComponent {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_mut_any(&mut self) -> &mut dyn AnyComponent {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Anchor {
|
||||
pub fn link(href: &str, html: Markup) -> Self {
|
||||
Anchor::new().with_href(href).with_html(html)
|
||||
}
|
||||
|
||||
pub fn button(href: &str, html: Markup) -> Self {
|
||||
Anchor::new().with_type(AnchorType::Button).with_href(href).with_html(html)
|
||||
}
|
||||
|
||||
pub fn location(id: &str) -> Self {
|
||||
Anchor::new().with_type(AnchorType::Location).with_id(id)
|
||||
}
|
||||
|
||||
// Anchor BUILDER.
|
||||
|
||||
pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self {
|
||||
self.alter_renderable(renderable);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_weight(mut self, weight: isize) -> Self {
|
||||
self.alter_weight(weight);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_type(mut self, anchor_type: AnchorType) -> Self {
|
||||
self.alter_type(anchor_type);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_href(mut self, href: &str) -> Self {
|
||||
self.alter_href(href);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_html(mut self, html: Markup) -> Self {
|
||||
self.alter_html(html);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_left_icon(mut self, icon: Icon) -> Self {
|
||||
self.alter_left_icon(icon);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_right_icon(mut self, icon: Icon) -> Self {
|
||||
self.alter_right_icon(icon);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_target(mut self, target: AnchorTarget) -> Self {
|
||||
self.alter_target(target);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_id(mut self, id: &str) -> Self {
|
||||
self.alter_id(id);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_classes(mut self, classes: &str, op: ClassesOp) -> Self {
|
||||
self.alter_classes(classes, op);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn using_template(mut self, template: &str) -> Self {
|
||||
self.alter_template(template);
|
||||
self
|
||||
}
|
||||
|
||||
// Anchor ALTER.
|
||||
|
||||
pub fn alter_renderable(&mut self, renderable: fn() -> bool) -> &mut Self {
|
||||
self.renderable = renderable;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_weight(&mut self, weight: isize) -> &mut Self {
|
||||
self.weight = weight;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_type(&mut self, anchor_type: AnchorType) -> &mut Self {
|
||||
self.anchor_type = anchor_type;
|
||||
self.classes.alter(match self.anchor_type {
|
||||
AnchorType::Button => "btn btn-primary",
|
||||
_ => "",
|
||||
}, ClassesOp::SetDefault);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_href(&mut self, href: &str) -> &mut Self {
|
||||
self.href.with_value(href);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_html(&mut self, html: Markup) -> &mut Self {
|
||||
self.html = html;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_left_icon(&mut self, icon: Icon) -> &mut Self {
|
||||
self.left_icon.clear();
|
||||
self.left_icon.add(icon);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_right_icon(&mut self, icon: Icon) -> &mut Self {
|
||||
self.right_icon.clear();
|
||||
self.right_icon.add(icon);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_target(&mut self, target: AnchorTarget) -> &mut Self {
|
||||
self.target = target;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_id(&mut self, id: &str) -> &mut Self {
|
||||
self.id.with_value(id);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_classes(&mut self, classes: &str, op: ClassesOp) -> &mut Self {
|
||||
self.classes.alter(classes, op);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_template(&mut self, template: &str) -> &mut Self {
|
||||
self.template = template.to_owned();
|
||||
self
|
||||
}
|
||||
|
||||
// Anchor GETTERS.
|
||||
|
||||
pub fn anchor_type(&self) -> &AnchorType {
|
||||
&self.anchor_type
|
||||
}
|
||||
|
||||
pub fn href(&self) -> &Option<String> {
|
||||
self.href.option()
|
||||
}
|
||||
|
||||
pub fn html(&self) -> &Markup {
|
||||
&self.html
|
||||
}
|
||||
|
||||
pub fn left_icon(&self) -> &AnchorIcon {
|
||||
&self.left_icon
|
||||
}
|
||||
|
||||
pub fn right_icon(&self) -> &AnchorIcon {
|
||||
&self.right_icon
|
||||
}
|
||||
|
||||
pub fn target(&self) -> &AnchorTarget {
|
||||
&self.target
|
||||
}
|
||||
|
||||
pub fn id(&self) -> &Option<String> {
|
||||
self.id.option()
|
||||
}
|
||||
|
||||
pub fn classes(&self) -> &Option<String> {
|
||||
self.classes.option()
|
||||
}
|
||||
|
||||
pub fn template(&self) -> &str {
|
||||
self.template.as_str()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,105 +1,158 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
pub const BLOCK_COMPONENT: &str = "pagetop::component::block";
|
||||
|
||||
pub struct Block {
|
||||
renderable: fn() -> bool,
|
||||
weight : i8,
|
||||
id : Option<String>,
|
||||
title : Option<String>,
|
||||
markup : Vec<Markup>,
|
||||
weight : isize,
|
||||
components: ComponentsBundle,
|
||||
title : OptAttr,
|
||||
id : OptIden,
|
||||
classes : Classes,
|
||||
template : String,
|
||||
}
|
||||
|
||||
impl PageComponent for Block {
|
||||
|
||||
fn prepare() -> Self {
|
||||
impl ComponentTrait for Block {
|
||||
fn new() -> Self {
|
||||
Block {
|
||||
renderable: always,
|
||||
renderable: render_always,
|
||||
weight : 0,
|
||||
id : None,
|
||||
title : None,
|
||||
markup : Vec::new(),
|
||||
components: ComponentsBundle::new(),
|
||||
title : OptAttr::new(),
|
||||
id : OptIden::new(),
|
||||
classes : Classes::new_with_default("block"),
|
||||
template : "default".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
fn handler(&self) -> &'static str {
|
||||
BLOCK_COMPONENT
|
||||
}
|
||||
|
||||
fn is_renderable(&self) -> bool {
|
||||
(self.renderable)()
|
||||
}
|
||||
|
||||
fn weight(&self) -> i8 {
|
||||
fn weight(&self) -> isize {
|
||||
self.weight
|
||||
}
|
||||
|
||||
fn default_render(&self, assets: &mut PageAssets) -> Markup {
|
||||
let id = assets.serial_id(self.name(), self.id());
|
||||
fn default_render(&self, context: &mut InContext) -> Markup {
|
||||
let id = context.required_id::<Block>(self.id());
|
||||
html! {
|
||||
div id=(id) class="block" {
|
||||
@if self.title != None {
|
||||
h2 class="block-title" { (self.title()) }
|
||||
div id=(id) class=[self.classes()] {
|
||||
@match self.title() {
|
||||
Some(title) => h2 class="block-title" { (title) },
|
||||
None => {}
|
||||
}
|
||||
div class="block-body" {
|
||||
@for markup in self.markup.iter() {
|
||||
(*markup)
|
||||
}
|
||||
(self.components().render(context))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn as_ref_any(&self) -> &dyn AnyComponent {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_mut_any(&mut self) -> &mut dyn AnyComponent {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Block {
|
||||
|
||||
pub fn markup(markup: Markup) -> Self {
|
||||
Block::prepare().add_markup(markup)
|
||||
// Block CONTAINER.
|
||||
|
||||
pub fn add(mut self, component: impl ComponentTrait) -> Self {
|
||||
self.components.add(component);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn components(&self) -> &ComponentsBundle {
|
||||
&self.components
|
||||
}
|
||||
|
||||
// Block BUILDER.
|
||||
|
||||
pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self {
|
||||
self.renderable = renderable;
|
||||
self.alter_renderable(renderable);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_weight(mut self, weight: i8) -> Self {
|
||||
self.weight = weight;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_id(mut self, id: &str) -> Self {
|
||||
self.id = util::valid_id(id);
|
||||
pub fn with_weight(mut self, weight: isize) -> Self {
|
||||
self.alter_weight(weight);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_title(mut self, title: &str) -> Self {
|
||||
self.title = util::optional_str(title);
|
||||
self.alter_title(title);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_markup(mut self, markup: Markup) -> Self {
|
||||
self.markup.push(markup);
|
||||
pub fn with_id(mut self, id: &str) -> Self {
|
||||
self.alter_id(id);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_classes(mut self, classes: &str, op: ClassesOp) -> Self {
|
||||
self.alter_classes(classes, op);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn using_template(mut self, template: &str) -> Self {
|
||||
self.alter_template(template);
|
||||
self
|
||||
}
|
||||
|
||||
// Block ALTER.
|
||||
|
||||
pub fn alter_renderable(&mut self, renderable: fn() -> bool) -> &mut Self {
|
||||
self.renderable = renderable;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_weight(&mut self, weight: isize) -> &mut Self {
|
||||
self.weight = weight;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_title(&mut self, title: &str) -> &mut Self {
|
||||
self.title.with_value(title);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_id(&mut self, id: &str) -> &mut Self {
|
||||
self.id.with_value(id);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_classes(&mut self, classes: &str, op: ClassesOp) -> &mut Self {
|
||||
self.classes.alter(classes, op);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_template(&mut self, template: &str) -> &mut Self {
|
||||
self.template = template.to_owned();
|
||||
self
|
||||
}
|
||||
|
||||
// Block GETTERS.
|
||||
|
||||
pub fn id(&self) -> &str {
|
||||
util::assigned_str(&self.id)
|
||||
pub fn title(&self) -> &Option<String> {
|
||||
self.title.option()
|
||||
}
|
||||
|
||||
pub fn title(&self) -> &str {
|
||||
util::assigned_str(&self.title)
|
||||
pub fn id(&self) -> &Option<String> {
|
||||
self.id.option()
|
||||
}
|
||||
|
||||
pub fn classes(&self) -> &Option<String> {
|
||||
self.classes.option()
|
||||
}
|
||||
|
||||
pub fn template(&self) -> &str {
|
||||
self.template.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
fn always() -> bool {
|
||||
true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,75 +1,105 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
pub const CHUNCK_COMPONENT: &str = "pagetop::component::chunck";
|
||||
|
||||
pub struct Chunck {
|
||||
renderable: fn() -> bool,
|
||||
weight : i8,
|
||||
markup : Vec<Markup>,
|
||||
weight : isize,
|
||||
html : Markup,
|
||||
template : String,
|
||||
}
|
||||
|
||||
impl PageComponent for Chunck {
|
||||
|
||||
fn prepare() -> Self {
|
||||
impl ComponentTrait for Chunck {
|
||||
fn new() -> Self {
|
||||
Chunck {
|
||||
renderable: always,
|
||||
renderable: render_always,
|
||||
weight : 0,
|
||||
markup : Vec::new(),
|
||||
html : html! {},
|
||||
template : "default".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
fn handler(&self) -> &'static str {
|
||||
CHUNCK_COMPONENT
|
||||
}
|
||||
|
||||
fn is_renderable(&self) -> bool {
|
||||
(self.renderable)()
|
||||
}
|
||||
|
||||
fn weight(&self) -> i8 {
|
||||
fn weight(&self) -> isize {
|
||||
self.weight
|
||||
}
|
||||
|
||||
fn default_render(&self, _: &mut PageAssets) -> Markup {
|
||||
html! {
|
||||
@for markup in self.markup.iter() {
|
||||
(*markup)
|
||||
}
|
||||
}
|
||||
fn default_render(&self, _: &mut InContext) -> Markup {
|
||||
html! { (*self.html()) }
|
||||
}
|
||||
|
||||
fn as_ref_any(&self) -> &dyn AnyComponent {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_mut_any(&mut self) -> &mut dyn AnyComponent {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Chunck {
|
||||
|
||||
pub fn markup(markup: Markup) -> Self {
|
||||
Chunck::prepare().add_markup(markup)
|
||||
pub fn with(html: Markup) -> Self {
|
||||
Chunck::new().with_html(html)
|
||||
}
|
||||
|
||||
// Chunck BUILDER.
|
||||
|
||||
pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self {
|
||||
self.renderable = renderable;
|
||||
self.alter_renderable(renderable);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_weight(mut self, weight: i8) -> Self {
|
||||
self.weight = weight;
|
||||
pub fn with_weight(mut self, weight: isize) -> Self {
|
||||
self.alter_weight(weight);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_markup(mut self, markup: Markup) -> Self {
|
||||
self.markup.push(markup);
|
||||
pub fn with_html(mut self, html: Markup) -> Self {
|
||||
self.alter_html(html);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn using_template(mut self, template: &str) -> Self {
|
||||
self.alter_template(template);
|
||||
self
|
||||
}
|
||||
|
||||
// Chunck ALTER.
|
||||
|
||||
pub fn alter_renderable(&mut self, renderable: fn() -> bool) -> &mut Self {
|
||||
self.renderable = renderable;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_weight(&mut self, weight: isize) -> &mut Self {
|
||||
self.weight = weight;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_html(&mut self, html: Markup) -> &mut Self {
|
||||
self.html = html;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_template(&mut self, template: &str) -> &mut Self {
|
||||
self.template = template.to_owned();
|
||||
self
|
||||
}
|
||||
|
||||
// Chunck GETTERS.
|
||||
|
||||
pub fn html(&self) -> &Markup {
|
||||
&self.html
|
||||
}
|
||||
|
||||
pub fn template(&self) -> &str {
|
||||
self.template.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
fn always() -> bool {
|
||||
true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,103 +1,212 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
enum ContainerType { Column, Row, Wrapper }
|
||||
pub const CONTAINER_COMPONENT: &str = "pagetop::component::container";
|
||||
|
||||
pub enum ContainerType { Header, Footer, Main, Section, Wrapper }
|
||||
|
||||
pub struct Container {
|
||||
renderable: fn() -> bool,
|
||||
weight : i8,
|
||||
id : Option<String>,
|
||||
container : ContainerType,
|
||||
components: PageContainer,
|
||||
template : String,
|
||||
renderable : fn() -> bool,
|
||||
weight : isize,
|
||||
components : ComponentsBundle,
|
||||
container : ContainerType,
|
||||
id : OptIden,
|
||||
classes : Classes,
|
||||
inner_classes: Classes,
|
||||
template : String,
|
||||
}
|
||||
|
||||
impl PageComponent for Container {
|
||||
|
||||
fn prepare() -> Self {
|
||||
impl ComponentTrait for Container {
|
||||
fn new() -> Self {
|
||||
Container {
|
||||
renderable: always,
|
||||
weight : 0,
|
||||
id : None,
|
||||
container : ContainerType::Wrapper,
|
||||
components: PageContainer::new(),
|
||||
template : "default".to_owned(),
|
||||
renderable : render_always,
|
||||
weight : 0,
|
||||
components : ComponentsBundle::new(),
|
||||
container : ContainerType::Wrapper,
|
||||
id : OptIden::new(),
|
||||
classes : Classes::new_with_default("container"),
|
||||
inner_classes: Classes::new_with_default("container"),
|
||||
template : "default".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
fn handler(&self) -> &'static str {
|
||||
CONTAINER_COMPONENT
|
||||
}
|
||||
|
||||
fn is_renderable(&self) -> bool {
|
||||
(self.renderable)()
|
||||
}
|
||||
|
||||
fn weight(&self) -> i8 {
|
||||
fn weight(&self) -> isize {
|
||||
self.weight
|
||||
}
|
||||
|
||||
fn default_render(&self, assets: &mut PageAssets) -> Markup {
|
||||
let classes = match self.container {
|
||||
ContainerType::Wrapper => "container",
|
||||
ContainerType::Row => "row",
|
||||
ContainerType::Column => "col",
|
||||
};
|
||||
html! {
|
||||
div id=[&self.id] class=(classes) {
|
||||
(self.components.render(assets))
|
||||
fn default_render(&self, context: &mut InContext) -> Markup {
|
||||
match self.container_type() {
|
||||
ContainerType::Header => html! {
|
||||
header id=[self.id()] class=[self.classes()] {
|
||||
div class=[self.inner_classes()] {
|
||||
(self.components().render(context))
|
||||
}
|
||||
}
|
||||
},
|
||||
ContainerType::Footer => html! {
|
||||
footer id=[self.id()] class=[self.classes()] {
|
||||
div class=[self.inner_classes()] {
|
||||
(self.components().render(context))
|
||||
}
|
||||
}
|
||||
},
|
||||
ContainerType::Main => html! {
|
||||
main id=[self.id()] class=[self.classes()] {
|
||||
div class=[self.inner_classes()] {
|
||||
(self.components().render(context))
|
||||
}
|
||||
}
|
||||
},
|
||||
ContainerType::Section => html! {
|
||||
section id=[self.id()] class=[self.classes()] {
|
||||
div class=[self.inner_classes()] {
|
||||
(self.components().render(context))
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => html! {
|
||||
div id=[self.id()] class=[self.classes()] {
|
||||
(self.components().render(context))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn as_ref_any(&self) -> &dyn AnyComponent {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_mut_any(&mut self) -> &mut dyn AnyComponent {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Container {
|
||||
|
||||
pub fn row() -> Self {
|
||||
let mut grid = Container::prepare();
|
||||
grid.container = ContainerType::Row;
|
||||
grid
|
||||
pub fn header() -> Self {
|
||||
let mut c = Container::new().with_classes("header", ClassesOp::SetDefault);
|
||||
c.container = ContainerType::Header;
|
||||
c
|
||||
}
|
||||
|
||||
pub fn column() -> Self {
|
||||
let mut grid = Container::prepare();
|
||||
grid.container = ContainerType::Column;
|
||||
grid
|
||||
pub fn footer() -> Self {
|
||||
let mut c = Container::new().with_classes("footer", ClassesOp::SetDefault);
|
||||
c.container = ContainerType::Footer;
|
||||
c
|
||||
}
|
||||
|
||||
pub fn main() -> Self {
|
||||
let mut c = Container::new().with_classes("main", ClassesOp::SetDefault);
|
||||
c.container = ContainerType::Main;
|
||||
c
|
||||
}
|
||||
|
||||
pub fn section() -> Self {
|
||||
let mut c = Container::new().with_classes("section", ClassesOp::SetDefault);
|
||||
c.container = ContainerType::Section;
|
||||
c
|
||||
}
|
||||
|
||||
// Container CONTAINER.
|
||||
|
||||
pub fn add(mut self, component: impl ComponentTrait) -> Self {
|
||||
self.components.add(component);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn components(&self) -> &ComponentsBundle {
|
||||
&self.components
|
||||
}
|
||||
|
||||
// Container BUILDER.
|
||||
|
||||
pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self {
|
||||
self.renderable = renderable;
|
||||
self.alter_renderable(renderable);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_weight(mut self, weight: i8) -> Self {
|
||||
self.weight = weight;
|
||||
pub fn with_weight(mut self, weight: isize) -> Self {
|
||||
self.alter_weight(weight);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_id(mut self, id: &str) -> Self {
|
||||
self.id = util::valid_id(id);
|
||||
self.alter_id(id);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add(mut self, component: impl PageComponent) -> Self {
|
||||
self.components.add(component);
|
||||
pub fn with_classes(mut self, classes: &str, op: ClassesOp) -> Self {
|
||||
self.alter_classes(classes, op);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_inner_classes(mut self, classes: &str, op: ClassesOp) -> Self {
|
||||
self.alter_inner_classes(classes, op);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn using_template(mut self, template: &str) -> Self {
|
||||
self.alter_template(template);
|
||||
self
|
||||
}
|
||||
|
||||
// Container ALTER.
|
||||
|
||||
pub fn alter_renderable(&mut self, renderable: fn() -> bool) -> &mut Self {
|
||||
self.renderable = renderable;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_weight(&mut self, weight: isize) -> &mut Self {
|
||||
self.weight = weight;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_id(&mut self, id: &str) -> &mut Self {
|
||||
self.id.with_value(id);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_classes(&mut self, classes: &str, op: ClassesOp) -> &mut Self {
|
||||
self.classes.alter(classes, op);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_inner_classes(&mut self, classes: &str, op: ClassesOp) -> &mut Self {
|
||||
self.inner_classes.alter(classes, op);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_template(&mut self, template: &str) -> &mut Self {
|
||||
self.template = template.to_owned();
|
||||
self
|
||||
}
|
||||
|
||||
// Container GETTERS.
|
||||
|
||||
pub fn id(&self) -> &str {
|
||||
util::assigned_str(&self.id)
|
||||
pub fn container_type(&self) -> &ContainerType {
|
||||
&self.container
|
||||
}
|
||||
|
||||
pub fn id(&self) -> &Option<String> {
|
||||
self.id.option()
|
||||
}
|
||||
|
||||
pub fn classes(&self) -> &Option<String> {
|
||||
self.classes.option()
|
||||
}
|
||||
|
||||
pub fn inner_classes(&self) -> &Option<String> {
|
||||
self.inner_classes.option()
|
||||
}
|
||||
|
||||
pub fn template(&self) -> &str {
|
||||
self.template.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
fn always() -> bool {
|
||||
true
|
||||
}
|
||||
|
|
|
|||
21
pagetop/src/base/component/form.rs
Normal file
21
pagetop/src/base/component/form.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
mod form;
|
||||
pub use form::{
|
||||
FORM_COMPONENT, Form, FormMethod
|
||||
};
|
||||
|
||||
mod input;
|
||||
pub use input::{
|
||||
INPUT_COMPONENT, Input, InputType
|
||||
};
|
||||
mod hidden;
|
||||
pub use hidden::{
|
||||
HIDDEN_COMPONENT, Hidden
|
||||
};
|
||||
mod date;
|
||||
pub use date::{
|
||||
DATE_COMPONENT, Date
|
||||
};
|
||||
mod button;
|
||||
pub use button::{
|
||||
BUTTON_COMPONENT, Button, ButtonType
|
||||
};
|
||||
|
|
@ -1,84 +1,103 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
enum ButtonType {Button, Reset, Submit}
|
||||
pub const BUTTON_COMPONENT: &str = "pagetop::component::form::button";
|
||||
|
||||
pub enum ButtonType {Button, Reset, Submit}
|
||||
|
||||
pub struct Button {
|
||||
renderable : fn() -> bool,
|
||||
weight : i8,
|
||||
weight : isize,
|
||||
button_type: ButtonType,
|
||||
name : Option<String>,
|
||||
value : Option<String>,
|
||||
autofocus : Option<String>,
|
||||
disabled : Option<String>,
|
||||
name : OptAttr,
|
||||
value : OptAttr,
|
||||
autofocus : OptAttr,
|
||||
disabled : OptAttr,
|
||||
classes : Classes,
|
||||
template : String,
|
||||
}
|
||||
|
||||
impl PageComponent for Button {
|
||||
|
||||
fn prepare() -> Self {
|
||||
impl ComponentTrait for Button {
|
||||
fn new() -> Self {
|
||||
Button {
|
||||
renderable : always,
|
||||
renderable : render_always,
|
||||
weight : 0,
|
||||
button_type: ButtonType::Button,
|
||||
name : None,
|
||||
value : None,
|
||||
autofocus : None,
|
||||
disabled : None,
|
||||
name : OptAttr::new(),
|
||||
value : OptAttr::new(),
|
||||
autofocus : OptAttr::new(),
|
||||
disabled : OptAttr::new(),
|
||||
classes : Classes::new_with_default("btn btn-primary"),
|
||||
template : "default".to_owned(),
|
||||
}
|
||||
.with_classes("form-button", ClassesOp::AddFirst)
|
||||
}
|
||||
|
||||
fn handler(&self) -> &'static str {
|
||||
BUTTON_COMPONENT
|
||||
}
|
||||
|
||||
fn is_renderable(&self) -> bool {
|
||||
(self.renderable)()
|
||||
}
|
||||
|
||||
fn weight(&self) -> i8 {
|
||||
fn weight(&self) -> isize {
|
||||
self.weight
|
||||
}
|
||||
|
||||
fn default_render(&self, _: &mut PageAssets) -> Markup {
|
||||
let (button_type, button_class) = match &self.button_type {
|
||||
ButtonType::Button => ("button", "btn btn-primary form-button"),
|
||||
ButtonType::Reset => ("reset", "btn btn-primary form-reset" ),
|
||||
ButtonType::Submit => ("submit", "btn btn-primary form-submit")
|
||||
fn default_render(&self, _: &mut InContext) -> Markup {
|
||||
let button_type = match self.button_type() {
|
||||
ButtonType::Button => "button",
|
||||
ButtonType::Reset => "reset",
|
||||
ButtonType::Submit => "submit",
|
||||
};
|
||||
let id_item = match &self.name {
|
||||
Some(name) => Some(format!("edit-{}", name)),
|
||||
let id = match self.name() {
|
||||
Some(name) => Some(concat_string!("edit-", name)),
|
||||
_ => None
|
||||
};
|
||||
html! {
|
||||
button
|
||||
type=(button_type)
|
||||
id=[&id_item]
|
||||
class=(button_class)
|
||||
name=[&self.name]
|
||||
value=[&self.value]
|
||||
autofocus=[&self.autofocus]
|
||||
disabled=[&self.disabled]
|
||||
id=[id]
|
||||
class=[self.classes()]
|
||||
name=[self.name()]
|
||||
value=[self.value()]
|
||||
autofocus=[self.autofocus()]
|
||||
disabled=[self.disabled()]
|
||||
{
|
||||
@match &self.value {
|
||||
Some(value) => (value),
|
||||
_ => ""
|
||||
};
|
||||
@match self.value() {
|
||||
Some(value) => { (value) },
|
||||
None => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn as_ref_any(&self) -> &dyn AnyComponent {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_mut_any(&mut self) -> &mut dyn AnyComponent {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Button {
|
||||
|
||||
pub fn button(value: &str) -> Self {
|
||||
Button::prepare().with_value(value)
|
||||
Button::new().with_value(value)
|
||||
}
|
||||
|
||||
pub fn reset(value: &str) -> Self {
|
||||
let mut button = Button::prepare().with_value(value);
|
||||
let mut button = Button::new()
|
||||
.with_classes("form-reset", ClassesOp::Replace("form-button"))
|
||||
.with_value(value);
|
||||
button.button_type = ButtonType::Reset;
|
||||
button
|
||||
}
|
||||
|
||||
pub fn submit(value: &str) -> Self {
|
||||
let mut button = Button::prepare().with_value(value);
|
||||
let mut button = Button::new()
|
||||
.with_classes("form-submit", ClassesOp::Replace("form-button"))
|
||||
.with_value(value);
|
||||
button.button_type = ButtonType::Submit;
|
||||
button
|
||||
}
|
||||
|
|
@ -86,75 +105,120 @@ impl Button {
|
|||
// Button BUILDER.
|
||||
|
||||
pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self {
|
||||
self.renderable = renderable;
|
||||
self.alter_renderable(renderable);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_weight(mut self, weight: i8) -> Self {
|
||||
self.weight = weight;
|
||||
pub fn with_weight(mut self, weight: isize) -> Self {
|
||||
self.alter_weight(weight);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_name(mut self, name: &str) -> Self {
|
||||
self.name = util::valid_id(name);
|
||||
self.alter_name(name);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_value(mut self, value: &str) -> Self {
|
||||
self.value = util::optional_str(value);
|
||||
self.alter_value(value);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn autofocus(mut self, toggle: bool) -> Self {
|
||||
self.autofocus = match toggle {
|
||||
true => Some("autofocus".to_owned()),
|
||||
false => None
|
||||
};
|
||||
pub fn with_autofocus(mut self, toggle: bool) -> Self {
|
||||
self.alter_autofocus(toggle);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn disabled(mut self, toggle: bool) -> Self {
|
||||
self.disabled = match toggle {
|
||||
true => Some("disabled".to_owned()),
|
||||
false => None
|
||||
};
|
||||
pub fn with_disabled(mut self, toggle: bool) -> Self {
|
||||
self.alter_disabled(toggle);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_classes(mut self, classes: &str, op: ClassesOp) -> Self {
|
||||
self.alter_classes(classes, op);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn using_template(mut self, template: &str) -> Self {
|
||||
self.alter_template(template);
|
||||
self
|
||||
}
|
||||
|
||||
// Button ALTER.
|
||||
|
||||
pub fn alter_renderable(&mut self, renderable: fn() -> bool) -> &mut Self {
|
||||
self.renderable = renderable;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_weight(&mut self, weight: isize) -> &mut Self {
|
||||
self.weight = weight;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_name(&mut self, name: &str) -> &mut Self {
|
||||
self.name.with_value(name);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_value(&mut self, value: &str) -> &mut Self {
|
||||
self.value.with_value(value);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_autofocus(&mut self, toggle: bool) -> &mut Self {
|
||||
self.autofocus.with_value(match toggle {
|
||||
true => "autofocus",
|
||||
false => "",
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_disabled(&mut self, toggle: bool) -> &mut Self {
|
||||
self.disabled.with_value(match toggle {
|
||||
true => "disabled",
|
||||
false => "",
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_classes(&mut self, classes: &str, op: ClassesOp) -> &mut Self {
|
||||
self.classes.alter(classes, op);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_template(&mut self, template: &str) -> &mut Self {
|
||||
self.template = template.to_owned();
|
||||
self
|
||||
}
|
||||
|
||||
// Button GETTERS.
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
util::assigned_str(&self.name)
|
||||
pub fn button_type(&self) -> &ButtonType {
|
||||
&self.button_type
|
||||
}
|
||||
|
||||
pub fn value(&self) -> &str {
|
||||
util::assigned_str(&self.value)
|
||||
pub fn name(&self) -> &Option<String> {
|
||||
self.name.option()
|
||||
}
|
||||
|
||||
pub fn has_autofocus(&self) -> bool {
|
||||
match &self.autofocus {
|
||||
Some(_) => true,
|
||||
_ => false
|
||||
}
|
||||
pub fn value(&self) -> &Option<String> {
|
||||
self.value.option()
|
||||
}
|
||||
|
||||
pub fn is_disabled(&self) -> bool {
|
||||
match &self.disabled {
|
||||
Some(_) => true,
|
||||
_ => false
|
||||
}
|
||||
pub fn autofocus(&self) -> &Option<String> {
|
||||
self.autofocus.option()
|
||||
}
|
||||
|
||||
pub fn disabled(&self) -> &Option<String> {
|
||||
self.disabled.option()
|
||||
}
|
||||
|
||||
pub fn classes(&self) -> &Option<String> {
|
||||
self.classes.option()
|
||||
}
|
||||
|
||||
pub fn template(&self) -> &str {
|
||||
self.template.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
fn always() -> bool {
|
||||
true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,95 +1,103 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
pub const DATE_COMPONENT: &str = "pagetop::component::form::date";
|
||||
|
||||
pub struct Date {
|
||||
renderable : fn() -> bool,
|
||||
weight : i8,
|
||||
name : Option<String>,
|
||||
value : Option<String>,
|
||||
label : Option<String>,
|
||||
placeholder : Option<String>,
|
||||
autofocus : Option<String>,
|
||||
autocomplete: Option<String>,
|
||||
disabled : Option<String>,
|
||||
readonly : Option<String>,
|
||||
required : Option<String>,
|
||||
help_text : Option<String>,
|
||||
weight : isize,
|
||||
name : OptAttr,
|
||||
value : OptAttr,
|
||||
label : OptAttr,
|
||||
placeholder : OptAttr,
|
||||
autofocus : OptAttr,
|
||||
autocomplete: OptAttr,
|
||||
disabled : OptAttr,
|
||||
readonly : OptAttr,
|
||||
required : OptAttr,
|
||||
help_text : OptAttr,
|
||||
classes : Classes,
|
||||
template : String,
|
||||
}
|
||||
|
||||
impl PageComponent for Date {
|
||||
|
||||
fn prepare() -> Self {
|
||||
impl ComponentTrait for Date {
|
||||
fn new() -> Self {
|
||||
Date {
|
||||
renderable : always,
|
||||
renderable : render_always,
|
||||
weight : 0,
|
||||
name : None,
|
||||
value : None,
|
||||
label : None,
|
||||
placeholder : None,
|
||||
autofocus : None,
|
||||
autocomplete: None,
|
||||
disabled : None,
|
||||
readonly : None,
|
||||
required : None,
|
||||
help_text : None,
|
||||
name : OptAttr::new(),
|
||||
value : OptAttr::new(),
|
||||
label : OptAttr::new(),
|
||||
placeholder : OptAttr::new(),
|
||||
autofocus : OptAttr::new(),
|
||||
autocomplete: OptAttr::new(),
|
||||
disabled : OptAttr::new(),
|
||||
readonly : OptAttr::new(),
|
||||
required : OptAttr::new(),
|
||||
help_text : OptAttr::new(),
|
||||
classes : Classes::new_with_default("form-item"),
|
||||
template : "default".to_owned(),
|
||||
}
|
||||
.with_classes("form-type-date", ClassesOp::AddFirst)
|
||||
}
|
||||
|
||||
fn handler(&self) -> &'static str {
|
||||
DATE_COMPONENT
|
||||
}
|
||||
|
||||
fn is_renderable(&self) -> bool {
|
||||
(self.renderable)()
|
||||
}
|
||||
|
||||
fn weight(&self) -> i8 {
|
||||
fn weight(&self) -> isize {
|
||||
self.weight
|
||||
}
|
||||
|
||||
fn default_render(&self, _: &mut PageAssets) -> Markup {
|
||||
let (class_item, id_item) = match &self.name {
|
||||
Some(name) => (
|
||||
format!("form-item form-item-{} form-type-date", name),
|
||||
Some(format!("edit-{}", name))
|
||||
),
|
||||
None => (
|
||||
"form-item form-type-date".to_owned(),
|
||||
None
|
||||
)
|
||||
fn default_render(&self, _: &mut InContext) -> Markup {
|
||||
let id = match self.name() {
|
||||
Some(name) => Some(concat_string!("edit-", name)),
|
||||
None => None,
|
||||
};
|
||||
html! {
|
||||
div class=(class_item) {
|
||||
@if self.label != None {
|
||||
label class="form-label" for=[&id_item] {
|
||||
(self.label()) " "
|
||||
@if self.required != None {
|
||||
span
|
||||
div class=[self.classes()] {
|
||||
@match self.label() {
|
||||
Some(label) => label class="form-label" for=[&id] {
|
||||
(label) " "
|
||||
@match self.required() {
|
||||
Some(_) => span
|
||||
class="form-required"
|
||||
title="Este campo es obligatorio."
|
||||
{
|
||||
"*"
|
||||
} " "
|
||||
title="Este campo es obligatorio." { "*" } " ",
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
input
|
||||
type="date"
|
||||
id=[&id_item]
|
||||
id=[id]
|
||||
class="form-control"
|
||||
name=[&self.name]
|
||||
value=[&self.value]
|
||||
placeholder=[&self.placeholder]
|
||||
autofocus=[&self.autofocus]
|
||||
autocomplete=[&self.autocomplete]
|
||||
readonly=[&self.readonly]
|
||||
required=[&self.required]
|
||||
disabled=[&self.disabled];
|
||||
@if self.help_text != None {
|
||||
div class="form-text" {
|
||||
(self.help_text())
|
||||
}
|
||||
name=[self.name()]
|
||||
value=[self.value()]
|
||||
placeholder=[self.placeholder()]
|
||||
autofocus=[self.autofocus()]
|
||||
autocomplete=[self.autocomplete()]
|
||||
readonly=[self.readonly()]
|
||||
required=[self.required()]
|
||||
disabled=[self.disabled()];
|
||||
@match self.help_text() {
|
||||
Some(help_text) => div class="form-text" { (help_text) },
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn as_ref_any(&self) -> &dyn AnyComponent {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_mut_any(&mut self) -> &mut dyn AnyComponent {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Date {
|
||||
|
|
@ -97,147 +105,209 @@ impl Date {
|
|||
// Date BUILDER.
|
||||
|
||||
pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self {
|
||||
self.renderable = renderable;
|
||||
self.alter_renderable(renderable);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_weight(mut self, weight: i8) -> Self {
|
||||
self.weight = weight;
|
||||
pub fn with_weight(mut self, weight: isize) -> Self {
|
||||
self.alter_weight(weight);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_name(mut self, name: &str) -> Self {
|
||||
self.name = util::valid_id(name);
|
||||
self.alter_name(name);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_value(mut self, value: &str) -> Self {
|
||||
self.value = util::optional_str(value);
|
||||
self.alter_value(value);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_label(mut self, label: &str) -> Self {
|
||||
self.label = util::optional_str(label);
|
||||
self.alter_label(label);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_placeholder(mut self, placeholder: &str) -> Self {
|
||||
self.placeholder = util::optional_str(placeholder);
|
||||
self.alter_placeholder(placeholder);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn autofocus(mut self, toggle: bool) -> Self {
|
||||
self.autofocus = match toggle {
|
||||
true => Some("autofocus".to_owned()),
|
||||
false => None
|
||||
};
|
||||
pub fn with_autofocus(mut self, toggle: bool) -> Self {
|
||||
self.alter_autofocus(toggle);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn autocomplete(mut self, toggle: bool) -> Self {
|
||||
self.autocomplete = match toggle {
|
||||
true => None,
|
||||
false => Some("off".to_owned())
|
||||
};
|
||||
pub fn with_autocomplete(mut self, toggle: bool) -> Self {
|
||||
self.alter_autocomplete(toggle);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn disabled(mut self, toggle: bool) -> Self {
|
||||
self.disabled = match toggle {
|
||||
true => Some("disabled".to_owned()),
|
||||
false => None
|
||||
};
|
||||
pub fn with_disabled(mut self, toggle: bool) -> Self {
|
||||
self.alter_disabled(toggle);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn readonly(mut self, toggle: bool) -> Self {
|
||||
self.readonly = match toggle {
|
||||
true => Some("readonly".to_owned()),
|
||||
false => None
|
||||
};
|
||||
pub fn with_readonly(mut self, toggle: bool) -> Self {
|
||||
self.alter_readonly(toggle);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn required(mut self, toggle: bool) -> Self {
|
||||
self.required = match toggle {
|
||||
true => Some("required".to_owned()),
|
||||
false => None
|
||||
};
|
||||
pub fn with_required(mut self, toggle: bool) -> Self {
|
||||
self.alter_required(toggle);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_help_text(mut self, help_text: &str) -> Self {
|
||||
self.help_text = util::optional_str(help_text);
|
||||
self.alter_help_text(help_text);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_classes(mut self, classes: &str, op: ClassesOp) -> Self {
|
||||
self.alter_classes(classes, op);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn using_template(mut self, template: &str) -> Self {
|
||||
self.alter_template(template);
|
||||
self
|
||||
}
|
||||
|
||||
// Date ALTER.
|
||||
|
||||
pub fn alter_renderable(&mut self, renderable: fn() -> bool) -> &mut Self {
|
||||
self.renderable = renderable;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_weight(&mut self, weight: isize) -> &mut Self {
|
||||
self.weight = weight;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_name(&mut self, name: &str) -> &mut Self {
|
||||
self.name.with_value(name);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_value(&mut self, value: &str) -> &mut Self {
|
||||
self.value.with_value(value);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_label(&mut self, label: &str) -> &mut Self {
|
||||
self.label.with_value(label);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_placeholder(&mut self, placeholder: &str) -> &mut Self {
|
||||
self.placeholder.with_value(placeholder);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_autofocus(&mut self, toggle: bool) -> &mut Self {
|
||||
self.autofocus.with_value(match toggle {
|
||||
true => "autofocus",
|
||||
false => "",
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_autocomplete(&mut self, toggle: bool) -> &mut Self {
|
||||
self.autocomplete.with_value(match toggle {
|
||||
true => "",
|
||||
false => "off",
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_disabled(&mut self, toggle: bool) -> &mut Self {
|
||||
self.disabled.with_value(match toggle {
|
||||
true => "disabled",
|
||||
false => "",
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_readonly(&mut self, toggle: bool) -> &mut Self {
|
||||
self.readonly.with_value(match toggle {
|
||||
true => "readonly",
|
||||
false => "",
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_required(&mut self, toggle: bool) -> &mut Self {
|
||||
self.required.with_value(match toggle {
|
||||
true => "required",
|
||||
false => "",
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_help_text(&mut self, help_text: &str) -> &mut Self {
|
||||
self.help_text.with_value(help_text);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_classes(&mut self, classes: &str, op: ClassesOp) -> &mut Self {
|
||||
self.classes.alter(classes, op);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_template(&mut self, template: &str) -> &mut Self {
|
||||
self.template = template.to_owned();
|
||||
self
|
||||
}
|
||||
|
||||
// Date GETTERS.
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
util::assigned_str(&self.name)
|
||||
pub fn name(&self) -> &Option<String> {
|
||||
self.name.option()
|
||||
}
|
||||
|
||||
pub fn value(&self) -> &str {
|
||||
util::assigned_str(&self.value)
|
||||
pub fn value(&self) -> &Option<String> {
|
||||
self.value.option()
|
||||
}
|
||||
|
||||
pub fn label(&self) -> &str {
|
||||
util::assigned_str(&self.label)
|
||||
pub fn label(&self) -> &Option<String> {
|
||||
self.label.option()
|
||||
}
|
||||
|
||||
pub fn placeholder(&self) -> &str {
|
||||
util::assigned_str(&self.placeholder)
|
||||
pub fn placeholder(&self) -> &Option<String> {
|
||||
self.placeholder.option()
|
||||
}
|
||||
|
||||
pub fn has_autofocus(&self) -> bool {
|
||||
match &self.autofocus {
|
||||
Some(_) => true,
|
||||
_ => false
|
||||
}
|
||||
pub fn autofocus(&self) -> &Option<String> {
|
||||
self.autofocus.option()
|
||||
}
|
||||
|
||||
pub fn has_autocomplete(&self) -> bool {
|
||||
match &self.autocomplete {
|
||||
Some(_) => false,
|
||||
_ => true
|
||||
}
|
||||
pub fn autocomplete(&self) -> &Option<String> {
|
||||
self.autocomplete.option()
|
||||
}
|
||||
|
||||
pub fn is_disabled(&self) -> bool {
|
||||
match &self.disabled {
|
||||
Some(_) => true,
|
||||
_ => false
|
||||
}
|
||||
pub fn disabled(&self) -> &Option<String> {
|
||||
self.disabled.option()
|
||||
}
|
||||
|
||||
pub fn is_readonly(&self) -> bool {
|
||||
match &self.readonly {
|
||||
Some(_) => true,
|
||||
_ => false
|
||||
}
|
||||
pub fn readonly(&self) -> &Option<String> {
|
||||
self.readonly.option()
|
||||
}
|
||||
|
||||
pub fn is_required(&self) -> bool {
|
||||
match &self.required {
|
||||
Some(_) => true,
|
||||
_ => false
|
||||
}
|
||||
pub fn required(&self) -> &Option<String> {
|
||||
self.required.option()
|
||||
}
|
||||
|
||||
pub fn help_text(&self) -> &str {
|
||||
util::assigned_str(&self.help_text)
|
||||
pub fn help_text(&self) -> &Option<String> {
|
||||
self.help_text.option()
|
||||
}
|
||||
|
||||
pub fn classes(&self) -> &Option<String> {
|
||||
self.classes.option()
|
||||
}
|
||||
|
||||
pub fn template(&self) -> &str {
|
||||
self.template.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
fn always() -> bool {
|
||||
true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,131 +1,195 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
pub const FORM_COMPONENT: &str = "pagetop::component::form";
|
||||
|
||||
pub enum FormMethod {Get, Post}
|
||||
|
||||
pub struct Form {
|
||||
renderable: fn() -> bool,
|
||||
weight : i8,
|
||||
id : Option<String>,
|
||||
action : Option<String>,
|
||||
weight : isize,
|
||||
elements : ComponentsBundle,
|
||||
action : OptAttr,
|
||||
charset : OptAttr,
|
||||
method : FormMethod,
|
||||
charset : Option<String>,
|
||||
elements : PageContainer,
|
||||
id : OptIden,
|
||||
classes : Classes,
|
||||
template : String,
|
||||
}
|
||||
|
||||
impl PageComponent for Form {
|
||||
|
||||
fn prepare() -> Self {
|
||||
impl ComponentTrait for Form {
|
||||
fn new() -> Self {
|
||||
Form {
|
||||
renderable: always,
|
||||
renderable: render_always,
|
||||
weight : 0,
|
||||
id : None,
|
||||
action : None,
|
||||
elements : ComponentsBundle::new(),
|
||||
action : OptAttr::new(),
|
||||
charset : OptAttr::new_with_value("UTF-8"),
|
||||
method : FormMethod::Post,
|
||||
charset : Some("UTF-8".to_owned()),
|
||||
elements : PageContainer::new(),
|
||||
id : OptIden::new(),
|
||||
classes : Classes::new_with_default("form"),
|
||||
template : "default".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
fn handler(&self) -> &'static str {
|
||||
FORM_COMPONENT
|
||||
}
|
||||
|
||||
fn is_renderable(&self) -> bool {
|
||||
(self.renderable)()
|
||||
}
|
||||
|
||||
fn weight(&self) -> i8 {
|
||||
fn weight(&self) -> isize {
|
||||
self.weight
|
||||
}
|
||||
|
||||
fn default_render(&self, assets: &mut PageAssets) -> Markup {
|
||||
let method = match self.method {
|
||||
fn default_render(&self, context: &mut InContext) -> Markup {
|
||||
let method = match self.method() {
|
||||
FormMethod::Get => None,
|
||||
FormMethod::Post => Some("post".to_owned())
|
||||
};
|
||||
html! {
|
||||
form
|
||||
id=[&self.id]
|
||||
action=[&self.action]
|
||||
id=[self.id()]
|
||||
class=[self.classes()]
|
||||
action=[self.action()]
|
||||
method=[method]
|
||||
accept-charset=[&self.charset]
|
||||
accept-charset=[self.charset()]
|
||||
{
|
||||
div {
|
||||
(self.elements.render(assets))
|
||||
}
|
||||
div { (self.elements().render(context)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn as_ref_any(&self) -> &dyn AnyComponent {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_mut_any(&mut self) -> &mut dyn AnyComponent {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Form {
|
||||
|
||||
// Form BUILDER.
|
||||
// Form CONTAINER.
|
||||
|
||||
pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self {
|
||||
self.renderable = renderable;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_weight(mut self, weight: i8) -> Self {
|
||||
self.weight = weight;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_id(mut self, id: &str) -> Self {
|
||||
self.id = util::valid_id(id);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_action(mut self, action: &str) -> Self {
|
||||
self.action = util::optional_str(action);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_method(mut self, method: FormMethod) -> Self {
|
||||
self.method = method;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_charset(mut self, charset: &str) -> Self {
|
||||
self.charset = util::optional_str(charset);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add(mut self, element: impl PageComponent) -> Self {
|
||||
pub fn add(mut self, element: impl ComponentTrait) -> Self {
|
||||
self.elements.add(element);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn elements(&self) -> &ComponentsBundle {
|
||||
&self.elements
|
||||
}
|
||||
|
||||
// Form BUILDER.
|
||||
|
||||
pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self {
|
||||
self.alter_renderable(renderable);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_weight(mut self, weight: isize) -> Self {
|
||||
self.alter_weight(weight);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_action(mut self, action: &str) -> Self {
|
||||
self.alter_action(action);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_charset(mut self, charset: &str) -> Self {
|
||||
self.alter_charset(charset);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_method(mut self, method: FormMethod) -> Self {
|
||||
self.alter_method(method);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_id(mut self, id: &str) -> Self {
|
||||
self.alter_id(id);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_classes(mut self, classes: &str, op: ClassesOp) -> Self {
|
||||
self.alter_classes(classes, op);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn using_template(mut self, template: &str) -> Self {
|
||||
self.alter_template(template);
|
||||
self
|
||||
}
|
||||
|
||||
// Form ALTER.
|
||||
|
||||
pub fn alter_renderable(&mut self, renderable: fn() -> bool) -> &mut Self {
|
||||
self.renderable = renderable;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_weight(&mut self, weight: isize) -> &mut Self {
|
||||
self.weight = weight;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_action(&mut self, action: &str) -> &mut Self {
|
||||
self.action.with_value(action);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_charset(&mut self, charset: &str) -> &mut Self {
|
||||
self.charset.with_value(charset);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_method(&mut self, method: FormMethod) -> &mut Self {
|
||||
self.method = method;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_id(&mut self, id: &str) -> &mut Self {
|
||||
self.id.with_value(id);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_classes(&mut self, classes: &str, op: ClassesOp) -> &mut Self {
|
||||
self.classes.alter(classes, op);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_template(&mut self, template: &str) -> &mut Self {
|
||||
self.template = template.to_owned();
|
||||
self
|
||||
}
|
||||
|
||||
// Form GETTERS.
|
||||
|
||||
pub fn id(&self) -> &str {
|
||||
util::assigned_str(&self.id)
|
||||
pub fn action(&self) -> &Option<String> {
|
||||
self.action.option()
|
||||
}
|
||||
|
||||
pub fn action(&self) -> &str {
|
||||
util::assigned_str(&self.action)
|
||||
pub fn charset(&self) -> &Option<String> {
|
||||
self.charset.option()
|
||||
}
|
||||
|
||||
pub fn method(&self) -> &str {
|
||||
match &self.method {
|
||||
FormMethod::Get => "get",
|
||||
FormMethod::Post => "post"
|
||||
}
|
||||
pub fn method(&self) -> &FormMethod {
|
||||
&self.method
|
||||
}
|
||||
|
||||
pub fn charset(&self) -> &str {
|
||||
util::assigned_str(&self.charset)
|
||||
pub fn id(&self) -> &Option<String> {
|
||||
self.id.option()
|
||||
}
|
||||
|
||||
pub fn classes(&self) -> &Option<String> {
|
||||
self.classes.option()
|
||||
}
|
||||
|
||||
pub fn template(&self) -> &str {
|
||||
self.template.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
fn always() -> bool {
|
||||
true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,70 +1,95 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
pub const HIDDEN_COMPONENT: &str = "pagetop::component::form::hidden";
|
||||
|
||||
pub struct Hidden {
|
||||
weight : i8,
|
||||
name : Option<String>,
|
||||
value : Option<String>,
|
||||
weight: isize,
|
||||
name : OptIden,
|
||||
value : OptAttr,
|
||||
}
|
||||
|
||||
impl PageComponent for Hidden {
|
||||
|
||||
fn prepare() -> Self {
|
||||
impl ComponentTrait for Hidden {
|
||||
fn new() -> Self {
|
||||
Hidden {
|
||||
weight : 0,
|
||||
name : None,
|
||||
value : None,
|
||||
weight: 0,
|
||||
name : OptIden::new(),
|
||||
value : OptAttr::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn weight(&self) -> i8 {
|
||||
fn handler(&self) -> &'static str {
|
||||
HIDDEN_COMPONENT
|
||||
}
|
||||
|
||||
fn weight(&self) -> isize {
|
||||
self.weight
|
||||
}
|
||||
|
||||
fn default_render(&self, _: &mut PageAssets) -> Markup {
|
||||
let id_item = match &self.name {
|
||||
Some(name) => Some(format!("value-{}", name)),
|
||||
fn default_render(&self, _: &mut InContext) -> Markup {
|
||||
let id = match self.name() {
|
||||
Some(name) => Some(concat_string!("value-", name)),
|
||||
_ => None
|
||||
};
|
||||
html! {
|
||||
input
|
||||
type="hidden"
|
||||
id=[&id_item]
|
||||
name=[&self.name]
|
||||
value=[&self.value];
|
||||
input type="hidden" id=[id] name=[self.name()] value=[self.value()];
|
||||
}
|
||||
}
|
||||
|
||||
fn as_ref_any(&self) -> &dyn AnyComponent {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_mut_any(&mut self) -> &mut dyn AnyComponent {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Hidden {
|
||||
|
||||
pub fn set(name: &str, value: &str) -> Self {
|
||||
Hidden::prepare().with_name(name).with_value(value)
|
||||
Hidden::new().with_name(name).with_value(value)
|
||||
}
|
||||
|
||||
// Hidden BUILDER.
|
||||
|
||||
pub fn with_weight(mut self, weight: i8) -> Self {
|
||||
self.weight = weight;
|
||||
pub fn with_weight(mut self, weight: isize) -> Self {
|
||||
self.alter_weight(weight);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_name(mut self, name: &str) -> Self {
|
||||
self.name = util::valid_id(name);
|
||||
self.alter_name(name);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_value(mut self, value: &str) -> Self {
|
||||
self.value = util::optional_str(value);
|
||||
self.alter_value(value);
|
||||
self
|
||||
}
|
||||
|
||||
// Hidden ALTER.
|
||||
|
||||
pub fn alter_weight(&mut self, weight: isize) -> &mut Self {
|
||||
self.weight = weight;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_name(&mut self, name: &str) -> &mut Self {
|
||||
self.name.with_value(name);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_value(&mut self, value: &str) -> &mut Self {
|
||||
self.value.with_value(value);
|
||||
self
|
||||
}
|
||||
|
||||
// Hidden GETTERS.
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
util::assigned_str(&self.name)
|
||||
pub fn name(&self) -> &Option<String> {
|
||||
self.name.option()
|
||||
}
|
||||
|
||||
pub fn value(&self) -> &str {
|
||||
util::assigned_str(&self.value)
|
||||
pub fn value(&self) -> &Option<String> {
|
||||
self.value.option()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,150 +1,162 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
enum InputType {Email, Password, Search, Telephone, Textfield, Url}
|
||||
pub const INPUT_COMPONENT: &str = "pagetop::component::form::input";
|
||||
|
||||
pub enum InputType {Email, Password, Search, Telephone, Textfield, Url}
|
||||
|
||||
pub struct Input {
|
||||
renderable : fn() -> bool,
|
||||
weight : i8,
|
||||
weight : isize,
|
||||
input_type : InputType,
|
||||
name : Option<String>,
|
||||
value : Option<String>,
|
||||
label : Option<String>,
|
||||
name : OptIden,
|
||||
value : OptAttr,
|
||||
label : OptAttr,
|
||||
size : Option<u16>,
|
||||
minlength : Option<u16>,
|
||||
maxlength : Option<u16>,
|
||||
placeholder : Option<String>,
|
||||
autofocus : Option<String>,
|
||||
autocomplete: Option<String>,
|
||||
disabled : Option<String>,
|
||||
readonly : Option<String>,
|
||||
required : Option<String>,
|
||||
help_text : Option<String>,
|
||||
placeholder : OptAttr,
|
||||
autofocus : OptAttr,
|
||||
autocomplete: OptAttr,
|
||||
disabled : OptAttr,
|
||||
readonly : OptAttr,
|
||||
required : OptAttr,
|
||||
help_text : OptAttr,
|
||||
classes : Classes,
|
||||
template : String,
|
||||
}
|
||||
|
||||
impl PageComponent for Input {
|
||||
|
||||
fn prepare() -> Self {
|
||||
impl ComponentTrait for Input {
|
||||
fn new() -> Self {
|
||||
Input {
|
||||
renderable : always,
|
||||
renderable : render_always,
|
||||
weight : 0,
|
||||
input_type : InputType::Textfield,
|
||||
name : None,
|
||||
value : None,
|
||||
label : None,
|
||||
name : OptIden::new(),
|
||||
value : OptAttr::new(),
|
||||
label : OptAttr::new(),
|
||||
size : Some(60),
|
||||
minlength : None,
|
||||
maxlength : Some(128),
|
||||
placeholder : None,
|
||||
autofocus : None,
|
||||
autocomplete: None,
|
||||
disabled : None,
|
||||
readonly : None,
|
||||
required : None,
|
||||
help_text : None,
|
||||
placeholder : OptAttr::new(),
|
||||
autofocus : OptAttr::new(),
|
||||
autocomplete: OptAttr::new(),
|
||||
disabled : OptAttr::new(),
|
||||
readonly : OptAttr::new(),
|
||||
required : OptAttr::new(),
|
||||
help_text : OptAttr::new(),
|
||||
classes : Classes::new_with_default("form-item"),
|
||||
template : "default".to_owned(),
|
||||
}
|
||||
.with_classes("form-type-textfield", ClassesOp::AddFirst)
|
||||
}
|
||||
|
||||
fn handler(&self) -> &'static str {
|
||||
INPUT_COMPONENT
|
||||
}
|
||||
|
||||
fn is_renderable(&self) -> bool {
|
||||
(self.renderable)()
|
||||
}
|
||||
|
||||
fn weight(&self) -> i8 {
|
||||
fn weight(&self) -> isize {
|
||||
self.weight
|
||||
}
|
||||
|
||||
fn default_render(&self, _: &mut PageAssets) -> Markup {
|
||||
let (input_type, class_type) = match &self.input_type {
|
||||
InputType::Email => ("email", "form-type-email"),
|
||||
InputType::Password => ("password", "form-type-password"),
|
||||
InputType::Search => ("search", "form-type-search"),
|
||||
InputType::Telephone => ("tel", "form-type-telephone"),
|
||||
InputType::Textfield => ("text", "form-type-textfield"),
|
||||
InputType::Url => ("url", "form-type-url")
|
||||
fn default_render(&self, _: &mut InContext) -> Markup {
|
||||
let type_input = match self.input_type() {
|
||||
InputType::Email => "email",
|
||||
InputType::Password => "password",
|
||||
InputType::Search => "search",
|
||||
InputType::Telephone => "tel",
|
||||
InputType::Textfield => "text",
|
||||
InputType::Url => "url",
|
||||
};
|
||||
let (class_item, id_item) = match &self.name {
|
||||
Some(name) => (
|
||||
format!("form-item form-item-{} {}", name, class_type),
|
||||
Some(format!("edit-{}", name))
|
||||
),
|
||||
None => (
|
||||
format!("form-item {}", class_type),
|
||||
None
|
||||
)
|
||||
let id = match self.name() {
|
||||
Some(name) => Some(concat_string!("edit-", name)),
|
||||
None => None,
|
||||
};
|
||||
html! {
|
||||
div class=(class_item) {
|
||||
@if self.label != None {
|
||||
label class="form-label" for=[&id_item] {
|
||||
(self.label()) " "
|
||||
@if self.required != None {
|
||||
span
|
||||
div class=[self.classes()] {
|
||||
@match self.label() {
|
||||
Some(label) => label class="form-label" for=[&id] {
|
||||
(label) " "
|
||||
@match self.required() {
|
||||
Some(_) => span
|
||||
class="form-required"
|
||||
title="Este campo es obligatorio."
|
||||
{
|
||||
"*"
|
||||
} " "
|
||||
title="Este campo es obligatorio." { "*" } " ",
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
input
|
||||
type=(input_type)
|
||||
id=[&id_item]
|
||||
type=(type_input)
|
||||
id=[id]
|
||||
class="form-control"
|
||||
name=[&self.name]
|
||||
value=[&self.value]
|
||||
size=[self.size]
|
||||
minlength=[self.minlength]
|
||||
maxlength=[self.maxlength]
|
||||
placeholder=[&self.placeholder]
|
||||
autofocus=[&self.autofocus]
|
||||
autocomplete=[&self.autocomplete]
|
||||
readonly=[&self.readonly]
|
||||
required=[&self.required]
|
||||
disabled=[&self.disabled];
|
||||
@if self.help_text != None {
|
||||
div class="form-text" {
|
||||
(self.help_text())
|
||||
}
|
||||
name=[self.name()]
|
||||
value=[self.value()]
|
||||
size=[self.size()]
|
||||
minlength=[self.minlength()]
|
||||
maxlength=[self.maxlength()]
|
||||
placeholder=[self.placeholder()]
|
||||
autofocus=[self.autofocus()]
|
||||
autocomplete=[self.autocomplete()]
|
||||
readonly=[self.readonly()]
|
||||
required=[self.required()]
|
||||
disabled=[self.disabled()];
|
||||
@match self.help_text() {
|
||||
Some(help_text) => div class="form-text" { (help_text) },
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn as_ref_any(&self) -> &dyn AnyComponent {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_mut_any(&mut self) -> &mut dyn AnyComponent {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Input {
|
||||
|
||||
pub fn textfield() -> Self {
|
||||
Input::prepare()
|
||||
Input::new()
|
||||
}
|
||||
|
||||
pub fn password() -> Self {
|
||||
let mut input = Input::prepare();
|
||||
let mut input = Input::new()
|
||||
.with_classes("form-type-password", ClassesOp::Replace("form-type-textfield"));
|
||||
input.input_type = InputType::Password;
|
||||
input
|
||||
}
|
||||
|
||||
pub fn search() -> Self {
|
||||
let mut input = Input::prepare();
|
||||
let mut input = Input::new()
|
||||
.with_classes("form-type-search", ClassesOp::Replace("form-type-textfield"));
|
||||
input.input_type = InputType::Search;
|
||||
input
|
||||
}
|
||||
|
||||
pub fn email() -> Self {
|
||||
let mut input = Input::prepare();
|
||||
let mut input = Input::new()
|
||||
.with_classes("form-type-email", ClassesOp::Replace("form-type-textfield"));
|
||||
input.input_type = InputType::Email;
|
||||
input
|
||||
}
|
||||
|
||||
pub fn telephone() -> Self {
|
||||
let mut input = Input::prepare();
|
||||
let mut input = Input::new()
|
||||
.with_classes("form-type-telephone", ClassesOp::Replace("form-type-textfield"));
|
||||
input.input_type = InputType::Telephone;
|
||||
input
|
||||
}
|
||||
|
||||
pub fn url() -> Self {
|
||||
let mut input = Input::prepare();
|
||||
let mut input = Input::new()
|
||||
.with_classes("form-type-url", ClassesOp::Replace("form-type-textfield"));
|
||||
input.input_type = InputType::Url;
|
||||
input
|
||||
}
|
||||
|
|
@ -152,112 +164,212 @@ impl Input {
|
|||
// Input BUILDER.
|
||||
|
||||
pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self {
|
||||
self.renderable = renderable;
|
||||
self.alter_renderable(renderable);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_weight(mut self, weight: i8) -> Self {
|
||||
self.weight = weight;
|
||||
pub fn with_weight(mut self, weight: isize) -> Self {
|
||||
self.alter_weight(weight);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_name(mut self, name: &str) -> Self {
|
||||
self.name = util::valid_id(name);
|
||||
self.alter_name(name);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_value(mut self, value: &str) -> Self {
|
||||
self.value = util::optional_str(value);
|
||||
self.alter_value(value);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_label(mut self, label: &str) -> Self {
|
||||
self.label = util::optional_str(label);
|
||||
self.alter_label(label);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_size(mut self, size: Option<u16>) -> Self {
|
||||
self.size = size;
|
||||
self.alter_size(size);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_minlength(mut self, minlength: Option<u16>) -> Self {
|
||||
self.minlength = minlength;
|
||||
self.alter_minlength(minlength);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_maxlength(mut self, maxlength: Option<u16>) -> Self {
|
||||
self.maxlength = maxlength;
|
||||
self.alter_maxlength(maxlength);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_placeholder(mut self, placeholder: &str) -> Self {
|
||||
self.placeholder = util::optional_str(placeholder);
|
||||
self.alter_placeholder(placeholder);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn autofocus(mut self, toggle: bool) -> Self {
|
||||
self.autofocus = match toggle {
|
||||
true => Some("autofocus".to_owned()),
|
||||
false => None
|
||||
};
|
||||
pub fn with_autofocus(mut self, toggle: bool) -> Self {
|
||||
self.alter_autofocus(toggle);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn autocomplete(mut self, toggle: bool) -> Self {
|
||||
self.autocomplete = match toggle {
|
||||
true => None,
|
||||
false => Some("off".to_owned())
|
||||
};
|
||||
pub fn with_autocomplete(mut self, toggle: bool) -> Self {
|
||||
self.alter_autocomplete(toggle);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn disabled(mut self, toggle: bool) -> Self {
|
||||
self.disabled = match toggle {
|
||||
true => Some("disabled".to_owned()),
|
||||
false => None
|
||||
};
|
||||
pub fn with_disabled(mut self, toggle: bool) -> Self {
|
||||
self.alter_disabled(toggle);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn readonly(mut self, toggle: bool) -> Self {
|
||||
self.readonly = match toggle {
|
||||
true => Some("readonly".to_owned()),
|
||||
false => None
|
||||
};
|
||||
pub fn with_readonly(mut self, toggle: bool) -> Self {
|
||||
self.alter_readonly(toggle);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn required(mut self, toggle: bool) -> Self {
|
||||
self.required = match toggle {
|
||||
true => Some("required".to_owned()),
|
||||
false => None
|
||||
};
|
||||
pub fn with_required(mut self, toggle: bool) -> Self {
|
||||
self.alter_required(toggle);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_help_text(mut self, help_text: &str) -> Self {
|
||||
self.help_text = util::optional_str(help_text);
|
||||
self.alter_help_text(help_text);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_classes(mut self, classes: &str, op: ClassesOp) -> Self {
|
||||
self.alter_classes(classes, op);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn using_template(mut self, template: &str) -> Self {
|
||||
self.alter_template(template);
|
||||
self
|
||||
}
|
||||
|
||||
// Input ALTER.
|
||||
|
||||
pub fn alter_renderable(&mut self, renderable: fn() -> bool) -> &mut Self {
|
||||
self.renderable = renderable;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_weight(&mut self, weight: isize) -> &mut Self {
|
||||
self.weight = weight;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_name(&mut self, name: &str) -> &mut Self {
|
||||
self.name.with_value(name);
|
||||
self.alter_classes(
|
||||
concat_string!("form-item form-item-", name).as_str(),
|
||||
ClassesOp::SetDefault
|
||||
);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_value(&mut self, value: &str) -> &mut Self {
|
||||
self.value.with_value(value);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_label(&mut self, label: &str) -> &mut Self {
|
||||
self.label.with_value(label);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_size(&mut self, size: Option<u16>) -> &mut Self {
|
||||
self.size = size;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_minlength(&mut self, minlength: Option<u16>) -> &mut Self {
|
||||
self.minlength = minlength;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_maxlength(&mut self, maxlength: Option<u16>) -> &mut Self {
|
||||
self.maxlength = maxlength;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_placeholder(&mut self, placeholder: &str) -> &mut Self {
|
||||
self.placeholder.with_value(placeholder);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_autofocus(&mut self, toggle: bool) -> &mut Self {
|
||||
self.autofocus.with_value(match toggle {
|
||||
true => "autofocus",
|
||||
false => "",
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_autocomplete(&mut self, toggle: bool) -> &mut Self {
|
||||
self.autocomplete.with_value(match toggle {
|
||||
true => "",
|
||||
false => "off",
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_disabled(&mut self, toggle: bool) -> &mut Self {
|
||||
self.disabled.with_value(match toggle {
|
||||
true => "disabled",
|
||||
false => "",
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_readonly(&mut self, toggle: bool) -> &mut Self {
|
||||
self.readonly.with_value(match toggle {
|
||||
true => "readonly",
|
||||
false => "",
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_required(&mut self, toggle: bool) -> &mut Self {
|
||||
self.required.with_value(match toggle {
|
||||
true => "required",
|
||||
false => "",
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_help_text(&mut self, help_text: &str) -> &mut Self {
|
||||
self.help_text.with_value(help_text);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_classes(&mut self, classes: &str, op: ClassesOp) -> &mut Self {
|
||||
self.classes.alter(classes, op);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_template(&mut self, template: &str) -> &mut Self {
|
||||
self.template = template.to_owned();
|
||||
self
|
||||
}
|
||||
|
||||
// Input GETTERS.
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
util::assigned_str(&self.name)
|
||||
pub fn input_type(&self) -> &InputType {
|
||||
&self.input_type
|
||||
}
|
||||
|
||||
pub fn value(&self) -> &str {
|
||||
util::assigned_str(&self.value)
|
||||
pub fn name(&self) -> &Option<String> {
|
||||
self.name.option()
|
||||
}
|
||||
|
||||
pub fn label(&self) -> &str {
|
||||
util::assigned_str(&self.label)
|
||||
pub fn value(&self) -> &Option<String> {
|
||||
self.value.option()
|
||||
}
|
||||
|
||||
pub fn label(&self) -> &Option<String> {
|
||||
self.label.option()
|
||||
}
|
||||
|
||||
pub fn size(&self) -> Option<u16> {
|
||||
|
|
@ -272,54 +384,39 @@ impl Input {
|
|||
self.maxlength
|
||||
}
|
||||
|
||||
pub fn placeholder(&self) -> &str {
|
||||
util::assigned_str(&self.placeholder)
|
||||
pub fn placeholder(&self) -> &Option<String> {
|
||||
self.placeholder.option()
|
||||
}
|
||||
|
||||
pub fn has_autofocus(&self) -> bool {
|
||||
match &self.autofocus {
|
||||
Some(_) => true,
|
||||
_ => false
|
||||
}
|
||||
pub fn autofocus(&self) -> &Option<String> {
|
||||
self.autofocus.option()
|
||||
}
|
||||
|
||||
pub fn has_autocomplete(&self) -> bool {
|
||||
match &self.autocomplete {
|
||||
Some(_) => false,
|
||||
_ => true
|
||||
}
|
||||
pub fn autocomplete(&self) -> &Option<String> {
|
||||
self.autocomplete.option()
|
||||
}
|
||||
|
||||
pub fn is_disabled(&self) -> bool {
|
||||
match &self.disabled {
|
||||
Some(_) => true,
|
||||
_ => false
|
||||
}
|
||||
pub fn disabled(&self) -> &Option<String> {
|
||||
self.disabled.option()
|
||||
}
|
||||
|
||||
pub fn is_readonly(&self) -> bool {
|
||||
match &self.readonly {
|
||||
Some(_) => true,
|
||||
_ => false
|
||||
}
|
||||
pub fn readonly(&self) -> &Option<String> {
|
||||
self.readonly.option()
|
||||
}
|
||||
|
||||
pub fn is_required(&self) -> bool {
|
||||
match &self.required {
|
||||
Some(_) => true,
|
||||
_ => false
|
||||
}
|
||||
pub fn required(&self) -> &Option<String> {
|
||||
self.required.option()
|
||||
}
|
||||
|
||||
pub fn help_text(&self) -> &str {
|
||||
util::assigned_str(&self.help_text)
|
||||
pub fn help_text(&self) -> &Option<String> {
|
||||
self.help_text.option()
|
||||
}
|
||||
|
||||
pub fn classes(&self) -> &Option<String> {
|
||||
self.classes.option()
|
||||
}
|
||||
|
||||
pub fn template(&self) -> &str {
|
||||
self.template.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
fn always() -> bool {
|
||||
true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
mod form;
|
||||
pub use form::{Form, FormMethod};
|
||||
|
||||
mod input;
|
||||
pub use input::Input;
|
||||
mod hidden;
|
||||
pub use hidden::Hidden;
|
||||
mod date;
|
||||
pub use date::Date;
|
||||
mod button;
|
||||
pub use button::Button;
|
||||
8
pagetop/src/base/component/grid.rs
Normal file
8
pagetop/src/base/component/grid.rs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
mod row;
|
||||
pub use row::{
|
||||
ROW_COMPONENT, Row
|
||||
};
|
||||
mod column;
|
||||
pub use column::{
|
||||
COLUMN_COMPONENT, Column
|
||||
};
|
||||
135
pagetop/src/base/component/grid/column.rs
Normal file
135
pagetop/src/base/component/grid/column.rs
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
pub const COLUMN_COMPONENT: &str = "pagetop::component::grid::column";
|
||||
|
||||
pub struct Column {
|
||||
renderable: fn() -> bool,
|
||||
weight : isize,
|
||||
components: ComponentsBundle,
|
||||
id : OptIden,
|
||||
classes : Classes,
|
||||
template : String,
|
||||
}
|
||||
|
||||
impl ComponentTrait for Column {
|
||||
fn new() -> Self {
|
||||
Column {
|
||||
renderable: render_always,
|
||||
weight : 0,
|
||||
components: ComponentsBundle::new(),
|
||||
id : OptIden::new(),
|
||||
classes : Classes::new_with_default("col"),
|
||||
template : "default".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
fn handler(&self) -> &'static str {
|
||||
COLUMN_COMPONENT
|
||||
}
|
||||
|
||||
fn is_renderable(&self) -> bool {
|
||||
(self.renderable)()
|
||||
}
|
||||
|
||||
fn weight(&self) -> isize {
|
||||
self.weight
|
||||
}
|
||||
|
||||
fn default_render(&self, context: &mut InContext) -> Markup {
|
||||
html! {
|
||||
div id=[self.id()] class=[self.classes()] {
|
||||
(self.components().render(context))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn as_ref_any(&self) -> &dyn AnyComponent {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_mut_any(&mut self) -> &mut dyn AnyComponent {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Column {
|
||||
|
||||
// Column CONTAINER.
|
||||
|
||||
pub fn add(mut self, component: impl ComponentTrait) -> Self {
|
||||
self.components.add(component);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn components(&self) -> &ComponentsBundle {
|
||||
&self.components
|
||||
}
|
||||
|
||||
// Column BUILDER.
|
||||
|
||||
pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self {
|
||||
self.alter_renderable(renderable);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_weight(mut self, weight: isize) -> Self {
|
||||
self.alter_weight(weight);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_id(mut self, id: &str) -> Self {
|
||||
self.alter_id(id);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_classes(mut self, classes: &str, op: ClassesOp) -> Self {
|
||||
self.alter_classes(classes, op);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn using_template(mut self, template: &str) -> Self {
|
||||
self.alter_template(template);
|
||||
self
|
||||
}
|
||||
|
||||
// Column ALTER.
|
||||
|
||||
pub fn alter_renderable(&mut self, renderable: fn() -> bool) -> &mut Self {
|
||||
self.renderable = renderable;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_weight(&mut self, weight: isize) -> &mut Self {
|
||||
self.weight = weight;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_id(&mut self, id: &str) -> &mut Self {
|
||||
self.id.with_value(id);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_classes(&mut self, classes: &str, op: ClassesOp) -> &mut Self {
|
||||
self.classes.alter(classes, op);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_template(&mut self, template: &str) -> &mut Self {
|
||||
self.template = template.to_owned();
|
||||
self
|
||||
}
|
||||
|
||||
// Column GETTERS.
|
||||
|
||||
pub fn id(&self) -> &Option<String> {
|
||||
self.id.option()
|
||||
}
|
||||
|
||||
pub fn classes(&self) -> &Option<String> {
|
||||
self.classes.option()
|
||||
}
|
||||
|
||||
pub fn template(&self) -> &str {
|
||||
self.template.as_str()
|
||||
}
|
||||
}
|
||||
135
pagetop/src/base/component/grid/row.rs
Normal file
135
pagetop/src/base/component/grid/row.rs
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
pub const ROW_COMPONENT: &str = "pagetop::component::grid::row";
|
||||
|
||||
pub struct Row {
|
||||
renderable: fn() -> bool,
|
||||
weight : isize,
|
||||
columns : ComponentsBundle,
|
||||
id : OptIden,
|
||||
classes : Classes,
|
||||
template : String,
|
||||
}
|
||||
|
||||
impl ComponentTrait for Row {
|
||||
fn new() -> Self {
|
||||
Row {
|
||||
renderable: render_always,
|
||||
weight : 0,
|
||||
columns : ComponentsBundle::new(),
|
||||
id : OptIden::new(),
|
||||
classes : Classes::new_with_default("row"),
|
||||
template : "default".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
fn handler(&self) -> &'static str {
|
||||
ROW_COMPONENT
|
||||
}
|
||||
|
||||
fn is_renderable(&self) -> bool {
|
||||
(self.renderable)()
|
||||
}
|
||||
|
||||
fn weight(&self) -> isize {
|
||||
self.weight
|
||||
}
|
||||
|
||||
fn default_render(&self, context: &mut InContext) -> Markup {
|
||||
html! {
|
||||
div id=[self.id()] class=[self.classes()] {
|
||||
(self.columns().render(context))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn as_ref_any(&self) -> &dyn AnyComponent {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_mut_any(&mut self) -> &mut dyn AnyComponent {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Row {
|
||||
|
||||
// Row CONTAINER.
|
||||
|
||||
pub fn add_column(mut self, column: grid::Column) -> Self {
|
||||
self.columns.add(column);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn columns(&self) -> &ComponentsBundle {
|
||||
&self.columns
|
||||
}
|
||||
|
||||
// Row BUILDER.
|
||||
|
||||
pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self {
|
||||
self.alter_renderable(renderable);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_weight(mut self, weight: isize) -> Self {
|
||||
self.alter_weight(weight);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_id(mut self, id: &str) -> Self {
|
||||
self.alter_id(id);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_classes(mut self, classes: &str, op: ClassesOp) -> Self {
|
||||
self.alter_classes(classes, op);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn using_template(mut self, template: &str) -> Self {
|
||||
self.alter_template(template);
|
||||
self
|
||||
}
|
||||
|
||||
// Row ALTER.
|
||||
|
||||
pub fn alter_renderable(&mut self, renderable: fn() -> bool) -> &mut Self {
|
||||
self.renderable = renderable;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_weight(&mut self, weight: isize) -> &mut Self {
|
||||
self.weight = weight;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_id(&mut self, id: &str) -> &mut Self {
|
||||
self.id.with_value(id);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_classes(&mut self, classes: &str, op: ClassesOp) -> &mut Self {
|
||||
self.classes.alter(classes, op);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_template(&mut self, template: &str) -> &mut Self {
|
||||
self.template = template.to_owned();
|
||||
self
|
||||
}
|
||||
|
||||
// Row GETTERS.
|
||||
|
||||
pub fn id(&self) -> &Option<String> {
|
||||
self.id.option()
|
||||
}
|
||||
|
||||
pub fn classes(&self) -> &Option<String> {
|
||||
self.classes.option()
|
||||
}
|
||||
|
||||
pub fn template(&self) -> &str {
|
||||
self.template.as_str()
|
||||
}
|
||||
}
|
||||
215
pagetop/src/base/component/heading.rs
Normal file
215
pagetop/src/base/component/heading.rs
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
pub const HEADING_COMPONENT: &str = "pagetop::component::heading";
|
||||
|
||||
pub enum HeadingType { H1, H2, H3, H4, H5, H6 }
|
||||
|
||||
pub enum HeadingDisplay {
|
||||
XxLarge,
|
||||
Large,
|
||||
Medium,
|
||||
Small,
|
||||
XxSmall,
|
||||
Normal,
|
||||
}
|
||||
|
||||
pub struct Heading {
|
||||
renderable: fn() -> bool,
|
||||
weight : isize,
|
||||
heading : HeadingType,
|
||||
html : Markup,
|
||||
display : HeadingDisplay,
|
||||
id : OptIden,
|
||||
classes : Classes,
|
||||
template : String,
|
||||
}
|
||||
|
||||
impl ComponentTrait for Heading {
|
||||
fn new() -> Self {
|
||||
Heading {
|
||||
renderable: render_always,
|
||||
weight : 0,
|
||||
heading : HeadingType::H1,
|
||||
html : html! {},
|
||||
display : HeadingDisplay::Normal,
|
||||
id : OptIden::new(),
|
||||
classes : Classes::new(),
|
||||
template : "default".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
fn handler(&self) -> &'static str {
|
||||
HEADING_COMPONENT
|
||||
}
|
||||
|
||||
fn is_renderable(&self) -> bool {
|
||||
(self.renderable)()
|
||||
}
|
||||
|
||||
fn weight(&self) -> isize {
|
||||
self.weight
|
||||
}
|
||||
|
||||
fn default_render(&self, _: &mut InContext) -> Markup {
|
||||
html! { @match &self.heading() {
|
||||
HeadingType::H1 => h1 id=[self.id()] class=[self.classes()] { (*self.html()) },
|
||||
HeadingType::H2 => h2 id=[self.id()] class=[self.classes()] { (*self.html()) },
|
||||
HeadingType::H3 => h3 id=[self.id()] class=[self.classes()] { (*self.html()) },
|
||||
HeadingType::H4 => h4 id=[self.id()] class=[self.classes()] { (*self.html()) },
|
||||
HeadingType::H5 => h5 id=[self.id()] class=[self.classes()] { (*self.html()) },
|
||||
HeadingType::H6 => h6 id=[self.id()] class=[self.classes()] { (*self.html()) },
|
||||
}}
|
||||
}
|
||||
|
||||
fn as_ref_any(&self) -> &dyn AnyComponent {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_mut_any(&mut self) -> &mut dyn AnyComponent {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Heading {
|
||||
pub fn h1(html: Markup) -> Self {
|
||||
Heading::new().with_heading(HeadingType::H1).with_html(html)
|
||||
}
|
||||
|
||||
pub fn h2(html: Markup) -> Self {
|
||||
Heading::new().with_heading(HeadingType::H2).with_html(html)
|
||||
}
|
||||
|
||||
pub fn h3(html: Markup) -> Self {
|
||||
Heading::new().with_heading(HeadingType::H3).with_html(html)
|
||||
}
|
||||
|
||||
pub fn h4(html: Markup) -> Self {
|
||||
Heading::new().with_heading(HeadingType::H4).with_html(html)
|
||||
}
|
||||
|
||||
pub fn h5(html: Markup) -> Self {
|
||||
Heading::new().with_heading(HeadingType::H5).with_html(html)
|
||||
}
|
||||
|
||||
pub fn h6(html: Markup) -> Self {
|
||||
Heading::new().with_heading(HeadingType::H6).with_html(html)
|
||||
}
|
||||
|
||||
// Heading BUILDER.
|
||||
|
||||
pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self {
|
||||
self.alter_renderable(renderable);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_weight(mut self, weight: isize) -> Self {
|
||||
self.alter_weight(weight);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_heading(mut self, heading: HeadingType) -> Self {
|
||||
self.alter_heading(heading);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_html(mut self, html: Markup) -> Self {
|
||||
self.alter_html(html);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_display(mut self, display: HeadingDisplay) -> Self {
|
||||
self.alter_display(display);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_id(mut self, id: &str) -> Self {
|
||||
self.alter_id(id);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_classes(mut self, classes: &str, op: ClassesOp) -> Self {
|
||||
self.alter_classes(classes, op);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn using_template(mut self, template: &str) -> Self {
|
||||
self.alter_template(template);
|
||||
self
|
||||
}
|
||||
|
||||
// Heading ALTER.
|
||||
|
||||
pub fn alter_renderable(&mut self, renderable: fn() -> bool) -> &mut Self {
|
||||
self.renderable = renderable;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_weight(&mut self, weight: isize) -> &mut Self {
|
||||
self.weight = weight;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_heading(&mut self, heading: HeadingType) -> &mut Self {
|
||||
self.heading = heading;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_html(&mut self, html: Markup) -> &mut Self {
|
||||
self.html = html;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_display(&mut self, display: HeadingDisplay) -> &mut Self {
|
||||
self.display = display;
|
||||
self.classes.alter(match &self.display() {
|
||||
HeadingDisplay::XxLarge => "display-2",
|
||||
HeadingDisplay::Large => "display-3",
|
||||
HeadingDisplay::Medium => "display-4",
|
||||
HeadingDisplay::Small => "display-5",
|
||||
HeadingDisplay::XxSmall => "display-6",
|
||||
HeadingDisplay::Normal => "",
|
||||
}, ClassesOp::SetDefault);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_id(&mut self, id: &str) -> &mut Self {
|
||||
self.id.with_value(id);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_classes(&mut self, classes: &str, op: ClassesOp) -> &mut Self {
|
||||
self.classes.alter(classes, op);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_template(&mut self, template: &str) -> &mut Self {
|
||||
self.template = template.to_owned();
|
||||
self
|
||||
}
|
||||
|
||||
// Paragraph GETTERS.
|
||||
|
||||
pub fn heading(&self) -> &HeadingType {
|
||||
&self.heading
|
||||
}
|
||||
|
||||
pub fn html(&self) -> &Markup {
|
||||
&self.html
|
||||
}
|
||||
|
||||
pub fn display(&self) -> &HeadingDisplay {
|
||||
&self.display
|
||||
}
|
||||
|
||||
pub fn id(&self) -> &Option<String> {
|
||||
self.id.option()
|
||||
}
|
||||
|
||||
pub fn classes(&self) -> &Option<String> {
|
||||
self.classes.option()
|
||||
}
|
||||
|
||||
pub fn template(&self) -> &str {
|
||||
self.template.as_str()
|
||||
}
|
||||
}
|
||||
95
pagetop/src/base/component/icon.rs
Normal file
95
pagetop/src/base/component/icon.rs
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
pub const ICON_COMPONENT: &str = "pagetop::component::icon";
|
||||
|
||||
pub struct Icon {
|
||||
renderable: fn() -> bool,
|
||||
weight : isize,
|
||||
icon_name : String,
|
||||
}
|
||||
|
||||
impl ComponentTrait for Icon {
|
||||
fn new() -> Self {
|
||||
Icon {
|
||||
renderable: render_always,
|
||||
weight : 0,
|
||||
icon_name : "question-circle-fill".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
fn handler(&self) -> &'static str {
|
||||
ICON_COMPONENT
|
||||
}
|
||||
|
||||
fn is_renderable(&self) -> bool {
|
||||
(self.renderable)()
|
||||
}
|
||||
|
||||
fn weight(&self) -> isize {
|
||||
self.weight
|
||||
}
|
||||
|
||||
fn default_render(&self, context: &mut InContext) -> Markup {
|
||||
context
|
||||
.add_stylesheet(StyleSheet::with_source(
|
||||
"/theme/icons/bootstrap-icons.css?ver=1.8.2"
|
||||
));
|
||||
|
||||
let name = concat_string!("bi-", self.icon_name);
|
||||
html! { i class=(name) {}; }
|
||||
}
|
||||
|
||||
fn as_ref_any(&self) -> &dyn AnyComponent {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_mut_any(&mut self) -> &mut dyn AnyComponent {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Icon {
|
||||
pub fn with(icon_name: &str) -> Self {
|
||||
Icon::new().with_icon_name(icon_name)
|
||||
}
|
||||
|
||||
// Icon BUILDER.
|
||||
|
||||
pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self {
|
||||
self.alter_renderable(renderable);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_weight(mut self, weight: isize) -> Self {
|
||||
self.alter_weight(weight);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_icon_name(mut self, name: &str) -> Self {
|
||||
self.alter_icon_name(name);
|
||||
self
|
||||
}
|
||||
|
||||
// Icon ALTER.
|
||||
|
||||
pub fn alter_renderable(&mut self, renderable: fn() -> bool) -> &mut Self {
|
||||
self.renderable = renderable;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_weight(&mut self, weight: isize) -> &mut Self {
|
||||
self.weight = weight;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_icon_name(&mut self, name: &str) -> &mut Self {
|
||||
self.icon_name = name.to_owned();
|
||||
self
|
||||
}
|
||||
|
||||
// Icon GETTERS.
|
||||
|
||||
pub fn icon_name(&self) -> &str {
|
||||
&self.icon_name
|
||||
}
|
||||
}
|
||||
142
pagetop/src/base/component/image.rs
Normal file
142
pagetop/src/base/component/image.rs
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
pub const IMAGE_COMPONENT: &str = "pagetop::component::image";
|
||||
|
||||
pub struct Image {
|
||||
renderable: fn() -> bool,
|
||||
weight : isize,
|
||||
source : OptAttr,
|
||||
id : OptIden,
|
||||
classes : Classes,
|
||||
template : String,
|
||||
}
|
||||
|
||||
impl ComponentTrait for Image {
|
||||
fn new() -> Self {
|
||||
Image {
|
||||
renderable: render_always,
|
||||
weight : 0,
|
||||
source : OptAttr::new(),
|
||||
id : OptIden::new(),
|
||||
classes : Classes::new_with_default("img-fluid"),
|
||||
template : "default".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
fn handler(&self) -> &'static str {
|
||||
IMAGE_COMPONENT
|
||||
}
|
||||
|
||||
fn is_renderable(&self) -> bool {
|
||||
(self.renderable)()
|
||||
}
|
||||
|
||||
fn weight(&self) -> isize {
|
||||
self.weight
|
||||
}
|
||||
|
||||
fn default_render(&self, _: &mut InContext) -> Markup {
|
||||
html! {
|
||||
img
|
||||
src=[self.source()]
|
||||
id=[self.id()]
|
||||
class=[self.classes()];
|
||||
}
|
||||
}
|
||||
|
||||
fn as_ref_any(&self) -> &dyn AnyComponent {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_mut_any(&mut self) -> &mut dyn AnyComponent {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Image {
|
||||
pub fn image(source: &str) -> Self {
|
||||
Image::new().with_source(source)
|
||||
}
|
||||
|
||||
// Image BUILDER.
|
||||
|
||||
pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self {
|
||||
self.alter_renderable(renderable);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_weight(mut self, weight: isize) -> Self {
|
||||
self.alter_weight(weight);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_source(mut self, source: &str) -> Self {
|
||||
self.alter_source(source);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_id(mut self, id: &str) -> Self {
|
||||
self.alter_id(id);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_classes(mut self, classes: &str, op: ClassesOp) -> Self {
|
||||
self.alter_classes(classes, op);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn using_template(mut self, template: &str) -> Self {
|
||||
self.alter_template(template);
|
||||
self
|
||||
}
|
||||
|
||||
// Image ALTER.
|
||||
|
||||
pub fn alter_renderable(&mut self, renderable: fn() -> bool) -> &mut Self {
|
||||
self.renderable = renderable;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_weight(&mut self, weight: isize) -> &mut Self {
|
||||
self.weight = weight;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_source(&mut self, source: &str) -> &mut Self {
|
||||
self.source.with_value(source);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_id(&mut self, id: &str) -> &mut Self {
|
||||
self.id.with_value(id);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_classes(&mut self, classes: &str, op: ClassesOp) -> &mut Self {
|
||||
self.classes.alter(classes, op);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_template(&mut self, template: &str) -> &mut Self {
|
||||
self.template = template.to_owned();
|
||||
self
|
||||
}
|
||||
|
||||
// Image GETTERS.
|
||||
|
||||
pub fn source(&self) -> &Option<String> {
|
||||
self.source.option()
|
||||
}
|
||||
|
||||
pub fn id(&self) -> &Option<String> {
|
||||
self.id.option()
|
||||
}
|
||||
|
||||
pub fn classes(&self) -> &Option<String> {
|
||||
self.classes.option()
|
||||
}
|
||||
|
||||
pub fn template(&self) -> &str {
|
||||
self.template.as_str()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,195 +1,228 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
enum MenuItemType {
|
||||
pub const MENU_COMPONENT: &str = "pagetop::component::menu";
|
||||
pub const MENUITEM_COMPONENT: &str = "pagetop::component::menu_item";
|
||||
|
||||
pub enum MenuItemType {
|
||||
Label(String),
|
||||
Link(String, String),
|
||||
LinkBlank(String, String),
|
||||
Markup(Markup),
|
||||
Html(Markup),
|
||||
Separator,
|
||||
Submenu(String, Menu),
|
||||
Void,
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// MenuItem.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
pub struct MenuItem {
|
||||
renderable: fn() -> bool,
|
||||
weight : i8,
|
||||
item_type : Option<MenuItemType>,
|
||||
weight : isize,
|
||||
item_type : MenuItemType,
|
||||
}
|
||||
|
||||
impl PageComponent for MenuItem {
|
||||
|
||||
fn prepare() -> Self {
|
||||
impl ComponentTrait for MenuItem {
|
||||
fn new() -> Self {
|
||||
MenuItem {
|
||||
renderable: always,
|
||||
renderable: render_always,
|
||||
weight : 0,
|
||||
item_type : None,
|
||||
item_type : MenuItemType::Void,
|
||||
}
|
||||
}
|
||||
|
||||
fn handler(&self) -> &'static str {
|
||||
MENUITEM_COMPONENT
|
||||
}
|
||||
|
||||
fn is_renderable(&self) -> bool {
|
||||
(self.renderable)()
|
||||
}
|
||||
|
||||
fn weight(&self) -> i8 {
|
||||
fn weight(&self) -> isize {
|
||||
self.weight
|
||||
}
|
||||
|
||||
fn default_render(&self, assets: &mut PageAssets) -> Markup {
|
||||
match &self.item_type {
|
||||
Some(MenuItemType::Label(label)) => html! {
|
||||
fn default_render(&self, context: &mut InContext) -> Markup {
|
||||
match self.item_type() {
|
||||
MenuItemType::Label(label) => html! {
|
||||
li class="label" { a href="#" { (label) } }
|
||||
},
|
||||
Some(MenuItemType::Link(label, path)) => html! {
|
||||
MenuItemType::Link(label, path) => html! {
|
||||
li class="link" { a href=(path) { (label) } }
|
||||
},
|
||||
Some(MenuItemType::LinkBlank(label, path)) => html! {
|
||||
MenuItemType::LinkBlank(label, path) => html! {
|
||||
li class="link_blank" {
|
||||
a href=(path) target="_blank" { (label) }
|
||||
}
|
||||
},
|
||||
Some(MenuItemType::Markup(markup)) => html! {
|
||||
li class="markup" { (*markup) }
|
||||
MenuItemType::Html(html) => html! {
|
||||
li class="html" { (*html) }
|
||||
},
|
||||
Some(MenuItemType::Submenu(label, menu)) => html! {
|
||||
MenuItemType::Submenu(label, menu) => html! {
|
||||
li class="submenu" {
|
||||
a href="#" { (label) }
|
||||
ul {
|
||||
(menu.render_items(assets))
|
||||
(menu.items().render(context))
|
||||
}
|
||||
}
|
||||
},
|
||||
Some(MenuItemType::Separator) => html! {
|
||||
MenuItemType::Separator => html! {
|
||||
li class="separator" { }
|
||||
},
|
||||
None => html! {}
|
||||
MenuItemType::Void => html! {},
|
||||
}
|
||||
}
|
||||
|
||||
fn as_ref_any(&self) -> &dyn AnyComponent {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_mut_any(&mut self) -> &mut dyn AnyComponent {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl MenuItem {
|
||||
|
||||
pub fn label(label: &str) -> Self {
|
||||
MenuItem {
|
||||
renderable: always,
|
||||
renderable: render_always,
|
||||
weight : 0,
|
||||
item_type : Some(MenuItemType::Label(label.to_owned())),
|
||||
item_type : MenuItemType::Label(label.to_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn link(label: &str, path: &str) -> Self {
|
||||
MenuItem {
|
||||
renderable: always,
|
||||
renderable: render_always,
|
||||
weight : 0,
|
||||
item_type : Some(MenuItemType::Link(
|
||||
item_type : MenuItemType::Link(
|
||||
label.to_owned(),
|
||||
path.to_owned(),
|
||||
)),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn link_blank(label: &str, path: &str) -> Self {
|
||||
MenuItem {
|
||||
renderable: always,
|
||||
renderable: render_always,
|
||||
weight : 0,
|
||||
item_type : Some(MenuItemType::LinkBlank(
|
||||
item_type : MenuItemType::LinkBlank(
|
||||
label.to_owned(),
|
||||
path.to_owned(),
|
||||
)),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn markup(markup: Markup) -> Self {
|
||||
pub fn html(html: Markup) -> Self {
|
||||
MenuItem {
|
||||
renderable: always,
|
||||
renderable: render_always,
|
||||
weight : 0,
|
||||
item_type : Some(MenuItemType::Markup(markup)),
|
||||
item_type : MenuItemType::Html(html),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn separator() -> Self {
|
||||
MenuItem {
|
||||
renderable: always,
|
||||
renderable: render_always,
|
||||
weight : 0,
|
||||
item_type : Some(MenuItemType::Separator),
|
||||
item_type : MenuItemType::Separator,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn submenu(label: &str, menu: Menu) -> Self {
|
||||
MenuItem {
|
||||
renderable: always,
|
||||
renderable: render_always,
|
||||
weight : 0,
|
||||
item_type : Some(MenuItemType::Submenu(
|
||||
item_type : MenuItemType::Submenu(
|
||||
label.to_owned(),
|
||||
menu
|
||||
)),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// MenuItem BUILDER.
|
||||
|
||||
pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self {
|
||||
self.alter_renderable(renderable);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_weight(mut self, weight: isize) -> Self {
|
||||
self.alter_weight(weight);
|
||||
self
|
||||
}
|
||||
|
||||
// MenuItem ALTER.
|
||||
|
||||
pub fn alter_renderable(&mut self, renderable: fn() -> bool) -> &mut Self {
|
||||
self.renderable = renderable;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_weight(mut self, weight: i8) -> Self {
|
||||
pub fn alter_weight(&mut self, weight: isize) -> &mut Self {
|
||||
self.weight = weight;
|
||||
self
|
||||
}
|
||||
|
||||
// MenuItem GETTERS.
|
||||
|
||||
pub fn item_type(&self) -> &MenuItemType {
|
||||
&self.item_type
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Menu.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
pub struct Menu {
|
||||
renderable: fn() -> bool,
|
||||
weight : i8,
|
||||
id : Option<String>,
|
||||
items : PageContainer,
|
||||
weight : isize,
|
||||
items : ComponentsBundle,
|
||||
id : OptIden,
|
||||
classes : Classes,
|
||||
template : String,
|
||||
}
|
||||
|
||||
impl PageComponent for Menu {
|
||||
|
||||
fn prepare() -> Self {
|
||||
impl ComponentTrait for Menu {
|
||||
fn new() -> Self {
|
||||
Menu {
|
||||
renderable: always,
|
||||
renderable: render_always,
|
||||
weight : 0,
|
||||
id : None,
|
||||
items : PageContainer::new(),
|
||||
items : ComponentsBundle::new(),
|
||||
id : OptIden::new(),
|
||||
classes : Classes::new_with_default("sm sm-clean"),
|
||||
template : "default".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
fn handler(&self) -> &'static str {
|
||||
MENU_COMPONENT
|
||||
}
|
||||
|
||||
fn is_renderable(&self) -> bool {
|
||||
(self.renderable)()
|
||||
}
|
||||
|
||||
fn weight(&self) -> i8 {
|
||||
fn weight(&self) -> isize {
|
||||
self.weight
|
||||
}
|
||||
|
||||
fn default_render(&self, assets: &mut PageAssets) -> Markup {
|
||||
assets
|
||||
.add_stylesheet(StyleSheet::source(
|
||||
fn default_render(&self, context: &mut InContext) -> Markup {
|
||||
context
|
||||
.add_stylesheet(StyleSheet::with_source(
|
||||
"/theme/menu/css/menu.css?ver=1.1.1"
|
||||
))
|
||||
.add_stylesheet(StyleSheet::source(
|
||||
.add_stylesheet(StyleSheet::with_source(
|
||||
"/theme/menu/css/menu-clean.css?ver=1.1.1"
|
||||
))
|
||||
.add_javascript(JavaScript::source(
|
||||
.add_javascript(JavaScript::with_source(
|
||||
"/theme/menu/js/menu.min.js?ver=1.1.1"
|
||||
))
|
||||
.add_jquery();
|
||||
|
||||
let id = assets.serial_id(self.name(), self.id());
|
||||
let id = context.required_id::<Menu>(self.id());
|
||||
html! {
|
||||
ul id=(id) class="sm sm-clean" {
|
||||
(self.render_items(assets))
|
||||
ul id=(id) class=[self.classes()] {
|
||||
(self.items().render(context))
|
||||
}
|
||||
script type="text/javascript" defer {
|
||||
"jQuery(function(){jQuery('#" (id) "').smartmenus({"
|
||||
|
|
@ -199,54 +232,94 @@ impl PageComponent for Menu {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn as_ref_any(&self) -> &dyn AnyComponent {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_mut_any(&mut self) -> &mut dyn AnyComponent {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Menu {
|
||||
|
||||
// Menu BUILDER.
|
||||
|
||||
pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self {
|
||||
self.renderable = renderable;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_weight(mut self, weight: i8) -> Self {
|
||||
self.weight = weight;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_id(mut self, id: &str) -> Self {
|
||||
self.id = util::valid_id(id);
|
||||
self
|
||||
}
|
||||
// Menu CONTAINER.
|
||||
|
||||
pub fn add(mut self, item: MenuItem) -> Self {
|
||||
self.items.add(item);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn items(&self) -> &ComponentsBundle {
|
||||
&self.items
|
||||
}
|
||||
|
||||
// Menu BUILDER.
|
||||
|
||||
pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self {
|
||||
self.alter_renderable(renderable);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_weight(mut self, weight: isize) -> Self {
|
||||
self.alter_weight(weight);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_id(mut self, id: &str) -> Self {
|
||||
self.alter_id(id);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_classes(mut self, classes: &str, op: ClassesOp) -> Self {
|
||||
self.alter_classes(classes, op);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn using_template(mut self, template: &str) -> Self {
|
||||
self.alter_template(template);
|
||||
self
|
||||
}
|
||||
|
||||
// Menu ALTER.
|
||||
|
||||
pub fn alter_renderable(&mut self, renderable: fn() -> bool) -> &mut Self {
|
||||
self.renderable = renderable;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_weight(&mut self, weight: isize) -> &mut Self {
|
||||
self.weight = weight;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_id(&mut self, id: &str) -> &mut Self {
|
||||
self.id.with_value(id);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_classes(&mut self, classes: &str, op: ClassesOp) -> &mut Self {
|
||||
self.classes.alter(classes, op);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_template(&mut self, template: &str) -> &mut Self {
|
||||
self.template = template.to_owned();
|
||||
self
|
||||
}
|
||||
|
||||
// Menu GETTERS.
|
||||
|
||||
pub fn id(&self) -> &str {
|
||||
util::assigned_str(&self.id)
|
||||
pub fn id(&self) -> &Option<String> {
|
||||
self.id.option()
|
||||
}
|
||||
|
||||
pub fn classes(&self) -> &Option<String> {
|
||||
self.classes.option()
|
||||
}
|
||||
|
||||
pub fn template(&self) -> &str {
|
||||
self.template.as_str()
|
||||
}
|
||||
|
||||
// Menu EXTRAS.
|
||||
|
||||
pub fn render_items(&self, assets: &mut PageAssets) -> Markup {
|
||||
html! { (self.items.render(assets)) }
|
||||
}
|
||||
}
|
||||
|
||||
fn always() -> bool {
|
||||
true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
mod container;
|
||||
pub use container::Container;
|
||||
mod chunck;
|
||||
pub use chunck::Chunck;
|
||||
mod block;
|
||||
pub use block::Block;
|
||||
mod menu;
|
||||
pub use menu::{Menu, MenuItem};
|
||||
|
||||
pub mod form;
|
||||
pub use form::{Form, FormMethod};
|
||||
172
pagetop/src/base/component/paragraph.rs
Normal file
172
pagetop/src/base/component/paragraph.rs
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
pub const PARAGRAPH_COMPONENT: &str = "pagetop::component::paragraph";
|
||||
|
||||
pub enum ParagraphDisplay {
|
||||
XxLarge,
|
||||
Large,
|
||||
Medium,
|
||||
Small,
|
||||
XxSmall,
|
||||
Normal,
|
||||
}
|
||||
|
||||
pub struct Paragraph {
|
||||
renderable: fn() -> bool,
|
||||
weight : isize,
|
||||
html : Markup,
|
||||
display : ParagraphDisplay,
|
||||
id : OptIden,
|
||||
classes : Classes,
|
||||
template : String,
|
||||
}
|
||||
|
||||
impl ComponentTrait for Paragraph {
|
||||
fn new() -> Self {
|
||||
Paragraph {
|
||||
renderable: render_always,
|
||||
weight : 0,
|
||||
html : html! {},
|
||||
display : ParagraphDisplay::Normal,
|
||||
id : OptIden::new(),
|
||||
classes : Classes::new(),
|
||||
template : "default".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
fn handler(&self) -> &'static str {
|
||||
PARAGRAPH_COMPONENT
|
||||
}
|
||||
|
||||
fn is_renderable(&self) -> bool {
|
||||
(self.renderable)()
|
||||
}
|
||||
|
||||
fn weight(&self) -> isize {
|
||||
self.weight
|
||||
}
|
||||
|
||||
fn default_render(&self, _: &mut InContext) -> Markup {
|
||||
html! {
|
||||
p id=[self.id()] class=[self.classes()] { (*self.html()) }
|
||||
}
|
||||
}
|
||||
|
||||
fn as_ref_any(&self) -> &dyn AnyComponent {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_mut_any(&mut self) -> &mut dyn AnyComponent {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Paragraph {
|
||||
pub fn with(html: Markup) -> Self {
|
||||
Paragraph::new().with_html(html)
|
||||
}
|
||||
|
||||
// Paragraph BUILDER.
|
||||
|
||||
pub fn with_renderable(mut self, renderable: fn() -> bool) -> Self {
|
||||
self.alter_renderable(renderable);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_weight(mut self, weight: isize) -> Self {
|
||||
self.alter_weight(weight);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_html(mut self, html: Markup) -> Self {
|
||||
self.alter_html(html);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_display(mut self, display: ParagraphDisplay) -> Self {
|
||||
self.alter_display(display);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_id(mut self, id: &str) -> Self {
|
||||
self.alter_id(id);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_classes(mut self, classes: &str, op: ClassesOp) -> Self {
|
||||
self.alter_classes(classes, op);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn using_template(mut self, template: &str) -> Self {
|
||||
self.alter_template(template);
|
||||
self
|
||||
}
|
||||
|
||||
// Paragraph ALTER.
|
||||
|
||||
pub fn alter_renderable(&mut self, renderable: fn() -> bool) -> &mut Self {
|
||||
self.renderable = renderable;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_weight(&mut self, weight: isize) -> &mut Self {
|
||||
self.weight = weight;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_html(&mut self, html: Markup) -> &mut Self {
|
||||
self.html = html;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_display(&mut self, display: ParagraphDisplay) -> &mut Self {
|
||||
self.display = display;
|
||||
self.classes.alter(match &self.display() {
|
||||
ParagraphDisplay::XxLarge => "fs-2",
|
||||
ParagraphDisplay::Large => "fs-3",
|
||||
ParagraphDisplay::Medium => "fs-4",
|
||||
ParagraphDisplay::Small => "fs-5",
|
||||
ParagraphDisplay::XxSmall => "fs-6",
|
||||
ParagraphDisplay::Normal => "",
|
||||
}, ClassesOp::SetDefault);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_id(&mut self, id: &str) -> &mut Self {
|
||||
self.id.with_value(id);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_classes(&mut self, classes: &str, op: ClassesOp) -> &mut Self {
|
||||
self.classes.alter(classes, op);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn alter_template(&mut self, template: &str) -> &mut Self {
|
||||
self.template = template.to_owned();
|
||||
self
|
||||
}
|
||||
|
||||
// Paragraph GETTERS.
|
||||
|
||||
pub fn html(&self) -> &Markup {
|
||||
&self.html
|
||||
}
|
||||
|
||||
pub fn display(&self) -> &ParagraphDisplay {
|
||||
&self.display
|
||||
}
|
||||
|
||||
pub fn id(&self) -> &Option<String> {
|
||||
self.id.option()
|
||||
}
|
||||
|
||||
pub fn classes(&self) -> &Option<String> {
|
||||
self.classes.option()
|
||||
}
|
||||
|
||||
pub fn template(&self) -> &str {
|
||||
self.template.as_str()
|
||||
}
|
||||
}
|
||||
1
pagetop/src/base/module.rs
Normal file
1
pagetop/src/base/module.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
pub mod demopage;
|
||||
|
|
@ -1,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))
|
||||
);
|
||||
}
|
||||
}
|
||||
230
pagetop/src/base/module/demopage.rs
Normal file
230
pagetop/src/base/module/demopage.rs
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
pub const DEMOPAGE_MODULE: &str = "pagetop::module::demopage";
|
||||
|
||||
localize!("src/base/module/demopage/locales");
|
||||
|
||||
pub struct Demopage;
|
||||
|
||||
impl ModuleTrait for Demopage {
|
||||
fn handler(&self) -> &'static str {
|
||||
DEMOPAGE_MODULE
|
||||
}
|
||||
|
||||
fn name(&self) -> String {
|
||||
l("module_name")
|
||||
}
|
||||
|
||||
fn description(&self) -> Option<String> {
|
||||
Some(l("module_description"))
|
||||
}
|
||||
|
||||
fn configure_service(&self, cfg: &mut app::web::ServiceConfig) {
|
||||
cfg.route("/", app::web::get().to(demo));
|
||||
}
|
||||
}
|
||||
|
||||
async fn demo() -> app::Result<Markup> {
|
||||
Page::new()
|
||||
.with_title(l("page_title").as_str())
|
||||
.add_to("content", hello_world())
|
||||
.add_to("content", hello_world_original())
|
||||
.add_to("content", just_visiting())
|
||||
.add_to("content", about_pagetop())
|
||||
.add_to("content", promo_pagetop())
|
||||
.add_to("content", reporting_problems())
|
||||
.render()
|
||||
}
|
||||
|
||||
fn hello_world() -> Container {
|
||||
Container::header()
|
||||
.add(grid::Row::new()
|
||||
.add_column(grid::Column::new()
|
||||
.add(Heading::h1(html! {
|
||||
(l("page_title"))
|
||||
}).with_display(HeadingDisplay::Large))
|
||||
.add(Paragraph::with(html! {
|
||||
(t("welcome_to", &args![
|
||||
"app" => SETTINGS.app.name.as_str()
|
||||
]))
|
||||
}))
|
||||
.add(Paragraph::with(html! {
|
||||
(e("welcome_intro", &args![
|
||||
"app" => format!("<strong>{}</strong>", &SETTINGS.app.name)
|
||||
]))
|
||||
}).with_display(ParagraphDisplay::Small))
|
||||
.add(Paragraph::with(html! {
|
||||
(e("welcome_pagetop", &args![
|
||||
"pagetop" => "<a href=\"https://pagetop-rs\">PageTop</a>"
|
||||
]))
|
||||
}))
|
||||
.add(Anchor::button("#",
|
||||
html! {
|
||||
("Offered services")
|
||||
}).with_left_icon(
|
||||
Icon::with("card-checklist")
|
||||
)
|
||||
)
|
||||
.add(Anchor::button("#",
|
||||
html! {
|
||||
("Get quote")
|
||||
}).with_left_icon(
|
||||
Icon::with("envelope-open-heart-fill")
|
||||
)
|
||||
)
|
||||
)
|
||||
.add_column(grid::Column::new()
|
||||
.add(Image::image("/bootsier/images/demo-header.svg"))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fn hello_world_original() -> Chunck {
|
||||
Chunck::with(html! {
|
||||
header id="header" class="header" {
|
||||
div class="container" {
|
||||
div class="row" {
|
||||
div class="col-lg-6 col-xl-5" {
|
||||
div class="text-container" {
|
||||
div class="section-title" {
|
||||
(t("welcome_to", &args![
|
||||
"app" => SETTINGS.app.name.as_str()
|
||||
]))
|
||||
}
|
||||
h1 class="h1-large" {
|
||||
(l("page_title"))
|
||||
}
|
||||
p class="p-large" {
|
||||
(e("welcome_intro", &args![
|
||||
"app" => format!(
|
||||
"<strong>{}</strong>",
|
||||
&SETTINGS.app.name
|
||||
)
|
||||
]))
|
||||
}
|
||||
p {
|
||||
(e("welcome_pagetop", &args![
|
||||
"pagetop" => "<a href=\"https://pagetop-rs\">PageTop</a>"
|
||||
]))
|
||||
}
|
||||
a class="btn-solid-lg" href="#services" {
|
||||
"Offered services"
|
||||
}
|
||||
a class="quote" href="#contact" {
|
||||
i class="fas fa-paper-plane" {}
|
||||
"Get quote"
|
||||
}
|
||||
}
|
||||
}
|
||||
div class="col-lg-6 col-xl-7" {
|
||||
div class="image-container" {
|
||||
img class="img-fluid" src="/bootsier/images/demo-header.svg" alt="alternative" {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn just_visiting() -> Chunck {
|
||||
Chunck::with(html! {
|
||||
div id="details" class="basic-1" {
|
||||
div class="container" {
|
||||
div class="row" {
|
||||
div class="col-lg-6 col-xl-7" {
|
||||
div class="image-container" {
|
||||
img class="img-fluid" src="/bootsier/images/demo-visiting.svg" alt="alternative" {}
|
||||
}
|
||||
}
|
||||
div class="col-lg-6 col-xl-5" {
|
||||
div class="text-container" {
|
||||
h2 {
|
||||
span {
|
||||
(l("visiting_title"))
|
||||
}
|
||||
br;
|
||||
(l("visiting_subtitle"))
|
||||
}
|
||||
p { (l("visiting_text1")) }
|
||||
p { (l("visiting_text2")) }
|
||||
a class="btn-solid-reg" data-bs-toggle="modal" data-bs-target="#staticBackdrop" { "Modal" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn about_pagetop() -> Chunck {
|
||||
Chunck::with(html! {
|
||||
div id="pagetop" class="basic-2" {
|
||||
div class="container" {
|
||||
div class="row" {
|
||||
div class="col-lg-6 col-xl-5" {
|
||||
div class="text-container" {
|
||||
h2 { (l("pagetop_title")) }
|
||||
p { (l("pagetop_text1")) }
|
||||
p { (l("pagetop_text2")) }
|
||||
p { (l("pagetop_text3")) }
|
||||
}
|
||||
}
|
||||
div class="col-lg-6 col-xl-7" {
|
||||
div class="image-container" {
|
||||
img class="img-fluid" src="/bootsier/images/demo-pagetop.svg" alt="alternative" {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn promo_pagetop() -> Chunck {
|
||||
Chunck::with(html! {
|
||||
div id="promo" class="basic-3" {
|
||||
div class="container" {
|
||||
div class="row" {
|
||||
div class="col-lg-6 col-xl-5" {
|
||||
div class="text-container" {
|
||||
h2 { (l("pagetop_promo_title")) }
|
||||
p { (e("pagetop_promo_text1", &args![
|
||||
"pagetop" =>
|
||||
"<a href=\"https://pagetop-rs\">PageTop</a>"
|
||||
])) }
|
||||
}
|
||||
}
|
||||
div class="col-lg-6 col-xl-7" {
|
||||
div class="image-container" {
|
||||
img class="img-fluid" src="/bootsier/images/demo-pagetop.svg" alt="alternative" {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn reporting_problems() -> Chunck {
|
||||
Chunck::with(html! {
|
||||
div id="reporting" class="basic-4" {
|
||||
div class="container" {
|
||||
div class="row" {
|
||||
div class="col-lg-6 col-xl-5" {
|
||||
div class="text-container" {
|
||||
h2 { (l("report_problems_title")) }
|
||||
p { (l("report_problems_text1")) }
|
||||
p { (l("report_problems_text2")) }
|
||||
}
|
||||
}
|
||||
div class="col-lg-6 col-xl-7" {
|
||||
div class="image-container" {
|
||||
img class="img-fluid" src="/bootsier/images/demo-pagetop.svg" alt="alternative" {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
25
pagetop/src/base/module/demopage/locales/en-US/demopage.ftl
Normal file
25
pagetop/src/base/module/demopage/locales/en-US/demopage.ftl
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
module_name = Default homepage
|
||||
module_description = Displays a demo homepage when none is configured.
|
||||
|
||||
page_title = Hello world!
|
||||
|
||||
welcome_to = Welcome to { $app }
|
||||
welcome_intro = This page is used to test the proper operation of { $app } after installation.
|
||||
welcome_pagetop = This web solution is powered by { $pagetop }.
|
||||
|
||||
visiting_title = Just visiting?
|
||||
visiting_subtitle = Are you user of this website?
|
||||
visiting_text1 = If you don't know what this page is about, this probably means that the site is either experiencing problems or is undergoing routine maintenance.
|
||||
visiting_text2 = If the problem persists, please contact your system administrator.
|
||||
|
||||
pagetop_title = About PageTop
|
||||
pagetop_text1 = If you can read this page, it means that the PageTop server is working properly, but has not yet been configured.
|
||||
pagetop_text2 = PageTop defines an interface for the most stable and popular Rust packages to build modular, extensible and configurable web solutions.
|
||||
pagetop_text3 = For information on PageTop please visit the "PageTop website".
|
||||
|
||||
pagetop_promo_title = Promoting PageTop
|
||||
pagetop_promo_text1 = You are free to use the image below on applications powered by { $pagetop }. Thanks for using PageTop!
|
||||
|
||||
report_problems_title = Reporting Problems
|
||||
report_problems_text1 = Please use the GitLab tool to report bugs in PageTop. However, check "existing bug reports" before reporting a new bug.
|
||||
report_problems_text2 = Please report bugs specific to modules (such as admin, and others) to respective packages, not to PageTop itself.
|
||||
25
pagetop/src/base/module/demopage/locales/es-ES/demopage.ftl
Normal file
25
pagetop/src/base/module/demopage/locales/es-ES/demopage.ftl
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
module_name = Página de inicio predeterminada
|
||||
module_description = Muestra una página de demostración predeterminada cuando no hay ninguna configurada.
|
||||
|
||||
page_title = ¡Hola mundo!
|
||||
|
||||
welcome_to = Bienvenido a { $app }
|
||||
welcome_intro = Esta página se utiliza para probar el correcto funcionamiento de { $app } después de la instalación.
|
||||
welcome_pagetop = Esta solución web funciona con { $pagetop }.
|
||||
|
||||
visiting_title = ¿Sólo de visita?
|
||||
visiting_subtitle = ¿Eres usuario de este sitio web?
|
||||
visiting_text1 = Si no sabes de qué trata esta página, probablemente significa que el sitio está experimentando problemas o está pasando por un mantenimiento de rutina.
|
||||
visiting_text2 = Si el problema persiste, póngase en contacto con el administrador del sistema.
|
||||
|
||||
pagetop_title = Sobre PageTop
|
||||
pagetop_text1 = Si puedes leer esta página, significa que el servidor PageTop funciona correctamente, pero aún no se ha configurado.
|
||||
pagetop_text2 = PageTop define una interfaz para los paquetes Rust más estables y populares para crear soluciones web modulares, extensibles y configurables.
|
||||
pagetop_text3 = Para obtener información sobre PageTop, visita el "sitio web de PageTop".
|
||||
|
||||
pagetop_promo_title = Promociona PageTop
|
||||
pagetop_promo_text1 = Puedes usar la siguiente imagen en aplicaciones desarrolladas sobre { $pagetop }. ¡Gracias por usar PageTop!
|
||||
|
||||
report_problems_title = Informando Problemas
|
||||
report_problems_text1 = Utilice la herramienta GitLab para informar errores en PageTop. Sin embargo, verifique los "informes de errores existentes" antes de informar de un nuevo error.
|
||||
report_problems_text2 = Informe los errores específicos de los módulos (como admin y otros) a los paquetes respectivos, no a PageTop en sí.
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
module_fullname = Default homepage
|
||||
module_description = Displays a default homepage when none is configured.
|
||||
|
||||
page_title = Hello world!
|
||||
|
||||
text_welcome = This page is used to test the proper operation of { $app } after installation. This web solution is powered by { $pagetop }. If you can read this page, it means that the PageTop server is working properly, but has not yet been configured.
|
||||
|
||||
title_normal_user = Just visiting?
|
||||
text1_normal_user = If you are a normal user of this web site and don't know what this page is about, this probably means that the site is either experiencing problems or is undergoing routine maintenance.
|
||||
text2_normal_user = If the problem persists, please contact your system administrator.
|
||||
|
||||
title_about_pagetop = About PageTop
|
||||
text1_about_pagetop = PageTop defines an interface for the most stable and popular Rust packages to build modular, extensible and configurable web solutions.
|
||||
text2_about_pagetop = For information on PageTop please visit the "PageTop website".
|
||||
|
||||
title_promo_pagetop = Promoting PageTop
|
||||
text1_promo_pagetop = You are free to use the image below on applications powered by { $pagetop }. Thanks for using PageTop!
|
||||
|
||||
title_report_problems = Reporting Problems
|
||||
text1_report_problems = Please use the GitLab tool to report bugs in PageTop. However, check "existing bug reports" before reporting a new bug.
|
||||
text2_report_problems = Please report bugs specific to modules (such as admin, and others) to respective packages, not to PageTop itself.
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
module_fullname = Página de inicio predeterminada
|
||||
module_description = Muestra una página de inicio predeterminada cuando no hay ninguna configurada.
|
||||
|
||||
page_title = ¡Hola mundo!
|
||||
|
||||
text_welcome = Esta página se utiliza para probar el correcto funcionamiento de { $app } después de la instalación. Esta solución web funciona con { $pagetop }. Si puede leer esta página, significa que el servidor PageTop funciona correctamente, pero aún no se ha configurado.
|
||||
|
||||
title_normal_user = ¿Sólo de visita?
|
||||
text1_normal_user = Si usted es un usuario normal de este sitio web y no sabe de qué trata esta página, probablemente significa que el sitio está experimentando problemas o está pasando por un mantenimiento de rutina.
|
||||
text2_normal_user = Si el problema persiste, póngase en contacto con el administrador del sistema.
|
||||
|
||||
title_about_pagetop = Sobre PageTop
|
||||
text1_about_pagetop = PageTop define una interfaz para los paquetes Rust más estables y populares para crear soluciones web modulares, extensibles y configurables.
|
||||
text2_about_pagetop = Para obtener información sobre PageTop, visite el "sitio web de PageTop".
|
||||
|
||||
title_promo_pagetop = Promociona PageTop
|
||||
text1_promo_pagetop = Puede usar la siguiente imagen en aplicaciones desarrolladas sobre { $pagetop }. ¡Gracias por usar PageTop!
|
||||
|
||||
title_report_problems = Informando Problemas
|
||||
text1_report_problems = Utilice la herramienta GitLab para informar errores en PageTop. Sin embargo, verifique los "informes de errores existentes" antes de informar de un nuevo error.
|
||||
text2_report_problems = Informe los errores específicos de los módulos (como admin y otros) a los paquetes respectivos, no a PageTop en sí.
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
localize!("en-US", "src/base/module/homepage/locales");
|
||||
|
||||
pub struct HomepageModule;
|
||||
|
||||
impl Module for HomepageModule {
|
||||
fn name(&self) -> &'static str {
|
||||
"homepage"
|
||||
}
|
||||
|
||||
fn fullname(&self) -> String {
|
||||
l("module_fullname")
|
||||
}
|
||||
|
||||
fn description(&self) -> Option<String> {
|
||||
Some(l("module_description"))
|
||||
}
|
||||
|
||||
fn configure_module(&self, cfg: &mut server::web::ServiceConfig) {
|
||||
cfg.route("/", server::web::get().to(home));
|
||||
}
|
||||
}
|
||||
|
||||
async fn home() -> server::Result<Markup> {
|
||||
Page::prepare()
|
||||
.with_title(
|
||||
l("page_title").as_str()
|
||||
)
|
||||
.add_to("content", Container::prepare()
|
||||
.with_id("welcome")
|
||||
.add(Chunck::markup(html! {
|
||||
h1 { (l("page_title")) }
|
||||
p { (e("text_welcome", &args![
|
||||
"app" => format!("<strong>{}</strong>", &SETTINGS.app.name),
|
||||
"pagetop" => "<a href=\"https://pagetop-rs\">PageTop</a>"
|
||||
])) }
|
||||
}))
|
||||
)
|
||||
.add_to("content", Container::prepare()
|
||||
.add(Container::row()
|
||||
.add(Container::column()
|
||||
.with_id("visitors")
|
||||
.add(Chunck::markup(html! {
|
||||
h2 { (l("title_normal_user")) }
|
||||
p { (l("text1_normal_user")) }
|
||||
p { (l("text2_normal_user")) }
|
||||
})))
|
||||
.add(Container::column()
|
||||
.with_id("pagetop")
|
||||
.add(Chunck::markup(html! {
|
||||
h2 { (l("title_about_pagetop")) }
|
||||
p { (l("text1_about_pagetop")) }
|
||||
p { (l("text2_about_pagetop")) }
|
||||
|
||||
h2 { (l("title_promo_pagetop")) }
|
||||
p { (e("text1_promo_pagetop", &args![
|
||||
"pagetop" =>
|
||||
"<a href=\"https://pagetop-rs\">PageTop</a>"
|
||||
])) }
|
||||
}))
|
||||
)
|
||||
)
|
||||
)
|
||||
.add_to("content", Container::prepare()
|
||||
.with_id("reporting")
|
||||
.add(Chunck::markup(html! {
|
||||
h2 { (l("title_report_problems")) }
|
||||
p { (l("text1_report_problems")) }
|
||||
p { (l("text2_report_problems")) }
|
||||
}))
|
||||
)
|
||||
.render()
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
pub mod admin;
|
||||
pub mod homepage;
|
||||
|
||||
#[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))]
|
||||
pub mod user;
|
||||
|
|
@ -1 +0,0 @@
|
|||
pub mod user;
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
use crate::db::entity::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Deserialize, Serialize)]
|
||||
#[sea_orm(table_name = "user")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
#[serde(skip_deserializing)]
|
||||
pub id: i32,
|
||||
pub title: String,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
use crate::db::migration::*;
|
||||
|
||||
#[derive(Iden)]
|
||||
enum User {
|
||||
Table,
|
||||
Id,
|
||||
Title,
|
||||
Text,
|
||||
}
|
||||
|
||||
pub struct Migration;
|
||||
|
||||
impl MigrationName for Migration {
|
||||
fn name(&self) -> &str {
|
||||
"m20220312_000001_create_table_user"
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(User::Table)
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(User::Id)
|
||||
.integer()
|
||||
.not_null()
|
||||
.auto_increment()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(User::Title)
|
||||
.string()
|
||||
.not_null()
|
||||
)
|
||||
.col(ColumnDef::new(User::Text)
|
||||
.string()
|
||||
.not_null()
|
||||
)
|
||||
.to_owned()
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop()
|
||||
.table(User::Table)
|
||||
.to_owned()
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
use crate::db::migration::*;
|
||||
|
||||
pub mod m20220312_000001_create_table_user;
|
||||
|
||||
pub struct Migrator;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigratorTrait for Migrator {
|
||||
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
|
||||
vec![Box::new(m20220312_000001_create_table_user::Migration)]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
localize!("en-US", "src/base/module/user/locales");
|
||||
|
||||
mod entity;
|
||||
mod migration;
|
||||
|
||||
pub struct UserModule;
|
||||
|
||||
impl Module for UserModule {
|
||||
fn name(&self) -> &'static str {
|
||||
"user"
|
||||
}
|
||||
|
||||
fn fullname(&self) -> String {
|
||||
l("module_fullname")
|
||||
}
|
||||
|
||||
fn description(&self) -> Option<String> {
|
||||
Some(l("module_description"))
|
||||
}
|
||||
|
||||
fn configure_module(&self, cfg: &mut server::web::ServiceConfig) {
|
||||
cfg.route("/user/login", server::web::get().to(login));
|
||||
}
|
||||
|
||||
fn migrations(&self, dbconn: &db::DbConn) -> Result<(), db::DbErr> {
|
||||
db_migrations!(dbconn)
|
||||
}
|
||||
}
|
||||
|
||||
fn form_login() -> impl PageComponent {
|
||||
Form::prepare()
|
||||
.with_id("user-login")
|
||||
.add(form::Input::textfield()
|
||||
.with_name("name")
|
||||
.with_label(l("username").as_str())
|
||||
.with_help_text(t("username_help", &args![
|
||||
"app" => SETTINGS.app.name.to_owned()
|
||||
]).as_str())
|
||||
.autofocus(true)
|
||||
)
|
||||
.add(form::Input::password()
|
||||
.with_name("pass")
|
||||
.with_label(l("password").as_str())
|
||||
.with_help_text(l("password_help").as_str())
|
||||
)
|
||||
.add(form::Button::submit(l("login").as_str()))
|
||||
}
|
||||
|
||||
async fn login() -> server::Result<Markup> {
|
||||
Page::prepare()
|
||||
.with_title(
|
||||
"Identificación del usuario"
|
||||
)
|
||||
.add_to("content", Container::prepare()
|
||||
.with_id("welcome")
|
||||
.add(form_login())
|
||||
)
|
||||
.render()
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
pub mod aliner;
|
||||
pub mod minimal;
|
||||
pub mod bootsier;
|
||||
pub mod bulmix;
|
||||
30
pagetop/src/base/theme/aliner.rs
Normal file
30
pagetop/src/base/theme/aliner.rs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
pub const ALINER_THEME: &str = "pagetop::theme::aliner";
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/aliner.rs"));
|
||||
|
||||
pub struct Aliner;
|
||||
|
||||
impl ThemeTrait for Aliner {
|
||||
fn handler(&self) -> &'static str {
|
||||
ALINER_THEME
|
||||
}
|
||||
|
||||
fn configure_service(&self, cfg: &mut app::web::ServiceConfig) {
|
||||
theme_static_files!(cfg, "/aliner");
|
||||
}
|
||||
|
||||
fn before_render_page(&self, page: &mut Page) {
|
||||
page.context()
|
||||
.with_favicon(Some(Favicon::new()
|
||||
.with_icon("/theme/favicon.png")
|
||||
))
|
||||
.add_stylesheet(
|
||||
StyleSheet::with_source(
|
||||
"/aliner/css/styles.css"
|
||||
)
|
||||
.with_weight(-99)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/aliner.rs"));
|
||||
|
||||
pub struct AlinerTheme;
|
||||
|
||||
impl Theme for AlinerTheme {
|
||||
fn name(&self) -> &'static str {
|
||||
"aliner"
|
||||
}
|
||||
|
||||
fn fullname(&self) -> String {
|
||||
"Aliner".to_owned()
|
||||
}
|
||||
|
||||
fn configure_theme(&self, cfg: &mut server::web::ServiceConfig) {
|
||||
cfg.service(actix_web_static_files::ResourceFiles::new(
|
||||
"/aliner",
|
||||
assets()
|
||||
));
|
||||
}
|
||||
|
||||
fn before_render_page(&self, page: &mut Page) {
|
||||
page.assets()
|
||||
.add_stylesheet(
|
||||
StyleSheet::source(
|
||||
"/aliner/css/styles.css"
|
||||
)
|
||||
.with_weight(-99)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,41 +1,35 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
pub const BOOTSIER_THEME: &str = "pagetop::theme::bootsier";
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/bootsier.rs"));
|
||||
|
||||
localize!("en-US", "src/base/theme/bootsier/locales");
|
||||
localize!("src/base/theme/bootsier/locales");
|
||||
|
||||
pub struct BootsierTheme;
|
||||
pub struct Bootsier;
|
||||
|
||||
impl Theme for BootsierTheme {
|
||||
fn name(&self) -> &'static str {
|
||||
"bootsier"
|
||||
impl ThemeTrait for Bootsier {
|
||||
fn handler(&self) -> &'static str {
|
||||
BOOTSIER_THEME
|
||||
}
|
||||
|
||||
fn fullname(&self) -> String {
|
||||
"Bootsier".to_owned()
|
||||
}
|
||||
|
||||
fn configure_theme(&self, cfg: &mut server::web::ServiceConfig) {
|
||||
cfg.service(actix_web_static_files::ResourceFiles::new(
|
||||
"/bootsier",
|
||||
assets()
|
||||
));
|
||||
fn configure_service(&self, cfg: &mut app::web::ServiceConfig) {
|
||||
theme_static_files!(cfg, "/bootsier");
|
||||
}
|
||||
|
||||
fn before_render_page(&self, page: &mut Page) {
|
||||
page.assets()
|
||||
.with_favicon(
|
||||
Favicon::new()
|
||||
.with_icon("/bootsier/favicon.png")
|
||||
)
|
||||
page.context()
|
||||
.with_favicon(Some(Favicon::new()
|
||||
.with_icon("/theme/favicon.png")
|
||||
))
|
||||
.add_stylesheet(
|
||||
StyleSheet::source(
|
||||
StyleSheet::with_source(
|
||||
"/bootsier/css/bootstrap.min.css?ver=5.1.3"
|
||||
)
|
||||
.with_weight(-99)
|
||||
)
|
||||
.add_javascript(
|
||||
JavaScript::source(
|
||||
JavaScript::with_source(
|
||||
"/bootsier/js/bootstrap.bundle.min.js?ver=5.1.3"
|
||||
)
|
||||
.with_weight(-99)
|
||||
|
|
@ -43,25 +37,25 @@ impl Theme for BootsierTheme {
|
|||
.add_jquery();
|
||||
}
|
||||
|
||||
fn render_error_page(&self, mut s: server::http::StatusCode) -> server::Result<Markup> {
|
||||
fn render_error_page(&self, mut s: app::http::StatusCode) -> app::Result<Markup> {
|
||||
let mut description = "e500-description";
|
||||
let mut message = "e500-description";
|
||||
match s {
|
||||
server::http::StatusCode::NOT_FOUND => {
|
||||
app::http::StatusCode::NOT_FOUND => {
|
||||
description = "e404-description";
|
||||
message = "e404-message";
|
||||
},
|
||||
_ => {
|
||||
s = server::http::StatusCode::INTERNAL_SERVER_ERROR;
|
||||
s = app::http::StatusCode::INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
}
|
||||
Page::prepare()
|
||||
Page::new()
|
||||
.with_title(format!("Error {}", s.as_str()).as_str())
|
||||
.add_to("content", Chunck::markup(html! {
|
||||
.add_to("content", Chunck::with(html! {
|
||||
div class="jumbotron" {
|
||||
div class="media" {
|
||||
img
|
||||
src="/bootsier/images/caution.png"
|
||||
src="/static/bootsier/images/caution.png"
|
||||
class="mr-4"
|
||||
style="width: 20%; max-width: 188px"
|
||||
alt="Caution!";
|
||||
78
pagetop/src/base/theme/bulmix.rs
Normal file
78
pagetop/src/base/theme/bulmix.rs
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
pub const BULMIX_THEME: &str = "pagetop::theme::bulmix";
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/bulmix.rs"));
|
||||
|
||||
pub struct Bulmix;
|
||||
|
||||
impl ThemeTrait for Bulmix {
|
||||
fn handler(&self) -> &'static str {
|
||||
BULMIX_THEME
|
||||
}
|
||||
|
||||
fn configure_service(&self, cfg: &mut app::web::ServiceConfig) {
|
||||
theme_static_files!(cfg, "/bulmix");
|
||||
}
|
||||
|
||||
fn before_render_page(&self, page: &mut Page) {
|
||||
page.context()
|
||||
.with_favicon(Some(Favicon::new()
|
||||
.with_icon("/theme/favicon.png")
|
||||
))
|
||||
.add_stylesheet(
|
||||
StyleSheet::with_source(
|
||||
"/bulmix/css/bulma.min.css?ver=0.9.3"
|
||||
)
|
||||
.with_weight(-99)
|
||||
)
|
||||
.add_jquery();
|
||||
}
|
||||
|
||||
fn before_render_component(
|
||||
&self,
|
||||
component: &mut dyn ComponentTrait,
|
||||
_context: &mut InContext
|
||||
) {
|
||||
match component.handler() {
|
||||
HEADING_COMPONENT => {
|
||||
let h = component_mut::<Heading>(component);
|
||||
h.alter_classes(concat_string!("title ", match h.display() {
|
||||
HeadingDisplay::XxLarge => "is-1",
|
||||
HeadingDisplay::Large => "is-2",
|
||||
HeadingDisplay::Medium => "is-3",
|
||||
HeadingDisplay::Small => "is-4",
|
||||
HeadingDisplay::XxSmall => "is-5",
|
||||
HeadingDisplay::Normal => "",
|
||||
}).as_str(), ClassesOp::SetDefault);
|
||||
},
|
||||
PARAGRAPH_COMPONENT => {
|
||||
let p = component_mut::<Paragraph>(component);
|
||||
p.alter_classes(match p.display() {
|
||||
ParagraphDisplay::XxLarge => "is-size-2",
|
||||
ParagraphDisplay::Large => "is-size-3",
|
||||
ParagraphDisplay::Medium => "is-size-4",
|
||||
ParagraphDisplay::Small => "is-size-5",
|
||||
ParagraphDisplay::XxSmall => "is-size-6",
|
||||
ParagraphDisplay::Normal => "",
|
||||
}, ClassesOp::SetDefault);
|
||||
},
|
||||
ANCHOR_COMPONENT => {
|
||||
let a = component_mut::<Anchor>(component);
|
||||
a.alter_classes(match a.anchor_type() {
|
||||
AnchorType::Button => "button is-primary",
|
||||
_ => "",
|
||||
}, ClassesOp::SetDefault);
|
||||
},
|
||||
grid::ROW_COMPONENT => {
|
||||
let row = component_mut::<grid::Row>(component);
|
||||
row.alter_classes("columns", ClassesOp::SetDefault);
|
||||
},
|
||||
grid::COLUMN_COMPONENT => {
|
||||
let col = component_mut::<grid::Column>(component);
|
||||
col.alter_classes("column content", ClassesOp::SetDefault);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
11
pagetop/src/base/theme/minimal.rs
Normal file
11
pagetop/src/base/theme/minimal.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
pub const MINIMAL_THEME: &str = "pagetop::theme::minimal";
|
||||
|
||||
pub struct Minimal;
|
||||
|
||||
impl ThemeTrait for Minimal {
|
||||
fn handler(&self) -> &'static str {
|
||||
MINIMAL_THEME
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
pub struct MinimalTheme;
|
||||
|
||||
impl Theme for MinimalTheme {
|
||||
fn name(&self) -> &'static str {
|
||||
"minimal"
|
||||
}
|
||||
|
||||
fn fullname(&self) -> String {
|
||||
"Minimal".to_owned()
|
||||
}
|
||||
}
|
||||
|
|
@ -45,15 +45,15 @@ pub static CONFIG: Lazy<Config> = Lazy::new(|| {
|
|||
/// seguros. Produce un *panic!* en caso de asignaciones no válidas.
|
||||
macro_rules! config_map {
|
||||
(
|
||||
$COMM:expr,
|
||||
$CONF:ident,
|
||||
$TYPE:tt
|
||||
$doc:expr,
|
||||
$SETTINGS:ident,
|
||||
$Type:tt
|
||||
$(, $key:expr => $value:expr)*
|
||||
) => {
|
||||
$crate::doc_comment! {
|
||||
concat!($COMM),
|
||||
concat!($doc),
|
||||
|
||||
pub static $CONF: $crate::Lazy<$TYPE> = $crate::Lazy::new(|| {
|
||||
pub static $SETTINGS: $crate::Lazy<$Type> = $crate::Lazy::new(|| {
|
||||
let mut settings = $crate::config::CONFIG.clone();
|
||||
$(
|
||||
settings.set_default($key, $value).unwrap();
|
||||
|
|
@ -104,12 +104,18 @@ pub struct Webserver {
|
|||
pub bind_port : u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Dev {
|
||||
pub static_files : String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Settings {
|
||||
pub app : App,
|
||||
pub log : Log,
|
||||
pub database : Database,
|
||||
pub webserver : Webserver,
|
||||
pub dev : Dev,
|
||||
}
|
||||
|
||||
config_map!(r#"
|
||||
|
|
@ -124,7 +130,7 @@ Ajustes globales y valores predeterminados para las secciones *\[app\]*,
|
|||
"app.theme" => "Bootsier",
|
||||
"app.language" => "en-US",
|
||||
"app.direction" => "ltr",
|
||||
"app.startup_banner" => "Small",
|
||||
"app.startup_banner" => "Slant",
|
||||
|
||||
// [log]
|
||||
"log.tracing" => "Info",
|
||||
|
|
@ -144,5 +150,8 @@ Ajustes globales y valores predeterminados para las secciones *\[app\]*,
|
|||
|
||||
// [webserver]
|
||||
"webserver.bind_address" => "localhost",
|
||||
"webserver.bind_port" => 8088
|
||||
"webserver.bind_port" => 8088,
|
||||
|
||||
// [dev]
|
||||
"dev.static_files" => ""
|
||||
);
|
||||
|
|
|
|||
4
pagetop/src/core.rs
Normal file
4
pagetop/src/core.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
pub mod component; // API to build new components.
|
||||
pub mod hook; // API to define functions that alter the behavior of PageTop core.
|
||||
pub mod module; // API to add new features with modules.
|
||||
pub mod theme; // API to create themes.
|
||||
28
pagetop/src/core/component.rs
Normal file
28
pagetop/src/core/component.rs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
mod hook;
|
||||
pub use hook::{
|
||||
BEFORE_RENDER_COMPONENT_HOOK,
|
||||
BeforeRenderComponentHook,
|
||||
};
|
||||
|
||||
mod context;
|
||||
pub use context::InContext;
|
||||
|
||||
mod definition;
|
||||
pub use definition::{
|
||||
AnyComponent,
|
||||
ComponentTrait,
|
||||
component_ref,
|
||||
component_mut,
|
||||
};
|
||||
use definition::render_component;
|
||||
|
||||
mod bundle;
|
||||
pub use bundle::ComponentsBundle;
|
||||
|
||||
mod all;
|
||||
pub use all::add_component_to;
|
||||
pub(crate) use all::common_components;
|
||||
|
||||
pub fn render_always() -> bool { true }
|
||||
|
||||
pub fn render_never() -> bool { false }
|
||||
22
pagetop/src/core/component/all.rs
Normal file
22
pagetop/src/core/component/all.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
use crate::Lazy;
|
||||
use super::{ComponentsBundle, ComponentTrait};
|
||||
|
||||
use std::sync::RwLock;
|
||||
use std::collections::HashMap;
|
||||
|
||||
static COMPONENTS: Lazy<RwLock<HashMap<&str, ComponentsBundle>>> = Lazy::new(|| {
|
||||
RwLock::new(HashMap::new())
|
||||
});
|
||||
|
||||
pub fn add_component_to(region: &'static str, component: impl ComponentTrait) {
|
||||
let mut hmap = COMPONENTS.write().unwrap();
|
||||
if let Some(regions) = hmap.get_mut(region) {
|
||||
regions.add(component);
|
||||
} else {
|
||||
hmap.insert(region, ComponentsBundle::new_with(component));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn common_components() -> HashMap<&'static str, ComponentsBundle> {
|
||||
COMPONENTS.read().unwrap().clone()
|
||||
}
|
||||
37
pagetop/src/core/component/bundle.rs
Normal file
37
pagetop/src/core/component/bundle.rs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
use crate::html::{Markup, html};
|
||||
use super::{InContext, ComponentTrait};
|
||||
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ComponentsBundle(Vec<Arc<RwLock<dyn ComponentTrait>>>);
|
||||
|
||||
impl ComponentsBundle {
|
||||
pub fn new() -> Self {
|
||||
ComponentsBundle(Vec::new())
|
||||
}
|
||||
|
||||
pub fn new_with(component: impl ComponentTrait) -> Self {
|
||||
let mut container = ComponentsBundle::new();
|
||||
container.add(component);
|
||||
container
|
||||
}
|
||||
|
||||
pub fn add(&mut self, component: impl ComponentTrait) {
|
||||
self.0.push(Arc::new(RwLock::new(component)));
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.0.clear();
|
||||
}
|
||||
|
||||
pub fn render(&self, context: &mut InContext) -> Markup {
|
||||
let mut components = self.0.clone();
|
||||
components.sort_by_key(|c| c.read().unwrap().weight());
|
||||
html! {
|
||||
@for c in components.iter() {
|
||||
(super::render_component(&mut *c.write().unwrap(), context))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
118
pagetop/src/core/component/context.rs
Normal file
118
pagetop/src/core/component/context.rs
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
use crate::{Lazy, base, concat_string, util};
|
||||
use crate::config::SETTINGS;
|
||||
use crate::html::*;
|
||||
use crate::core::theme::ThemeTrait;
|
||||
use crate::core::theme::all::theme_by_single_name;
|
||||
|
||||
static DEFAULT_THEME: Lazy<&dyn ThemeTrait> = Lazy::new(|| {
|
||||
match theme_by_single_name(&SETTINGS.app.theme) {
|
||||
Some(theme) => theme,
|
||||
None => &base::theme::bootsier::Bootsier,
|
||||
}
|
||||
});
|
||||
|
||||
pub struct InContext {
|
||||
theme : &'static dyn ThemeTrait,
|
||||
favicon : Option<Favicon>,
|
||||
metadata : Vec<(String, String)>,
|
||||
stylesheets: Assets<StyleSheet>,
|
||||
javascripts: Assets<JavaScript>,
|
||||
with_jquery: bool,
|
||||
id_counter : usize,
|
||||
}
|
||||
|
||||
impl InContext {
|
||||
pub fn new() -> Self {
|
||||
InContext {
|
||||
theme : *DEFAULT_THEME,
|
||||
favicon : None,
|
||||
metadata : Vec::new(),
|
||||
stylesheets: Assets::<StyleSheet>::new(),
|
||||
javascripts: Assets::<JavaScript>::new(),
|
||||
with_jquery: false,
|
||||
id_counter : 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn using_theme(&mut self, theme_name: &str) -> &mut Self {
|
||||
self.theme = theme_by_single_name(theme_name).unwrap_or(*DEFAULT_THEME);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_favicon(&mut self, favicon: Option<Favicon>) -> &mut Self {
|
||||
self.favicon = favicon;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_metadata(&mut self, name: String, content: String) -> &mut Self {
|
||||
self.metadata.push((name, content));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_stylesheet(&mut self, css: StyleSheet) -> &mut Self {
|
||||
self.stylesheets.add(css);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_javascript(&mut self, js: JavaScript) -> &mut Self {
|
||||
self.javascripts.add(js);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_jquery(&mut self) -> &mut Self {
|
||||
if !self.with_jquery {
|
||||
self.add_javascript(
|
||||
JavaScript::with_source(
|
||||
"/theme/js/jquery.min.js?ver=3.6.0"
|
||||
)
|
||||
.with_weight(isize::MIN)
|
||||
.with_mode(JSMode::Normal)
|
||||
);
|
||||
self.with_jquery = true;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// InContext GETTERS.
|
||||
|
||||
pub(crate) fn theme(&mut self) -> &'static dyn ThemeTrait {
|
||||
self.theme
|
||||
}
|
||||
|
||||
/// InContext RENDER.
|
||||
|
||||
pub fn render(&mut self) -> Markup {
|
||||
html! {
|
||||
@match &self.favicon {
|
||||
Some(favicon) => (favicon.render()),
|
||||
None => "",
|
||||
}
|
||||
@for (name, content) in &self.metadata {
|
||||
meta name=(name) content=(content) {}
|
||||
}
|
||||
(self.stylesheets.render())
|
||||
(self.javascripts.render())
|
||||
}
|
||||
}
|
||||
|
||||
// InContext EXTRAS.
|
||||
|
||||
pub fn required_id<T>(&mut self, id: &Option<String>) -> String {
|
||||
match id {
|
||||
Some(id) => id.to_string(),
|
||||
None => {
|
||||
let prefix = util::single_type_name::<T>()
|
||||
.trim()
|
||||
.replace(" ", "_")
|
||||
.to_lowercase();
|
||||
let prefix = if prefix.is_empty() {
|
||||
"prefix".to_owned()
|
||||
} else {
|
||||
prefix
|
||||
};
|
||||
self.id_counter += 1;
|
||||
concat_string!(prefix, "-", self.id_counter.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
66
pagetop/src/core/component/definition.rs
Normal file
66
pagetop/src/core/component/definition.rs
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
use crate::util;
|
||||
use crate::html::{Markup, html};
|
||||
use crate::core::hook::{hook_ref, run_hooks};
|
||||
use super::{BEFORE_RENDER_COMPONENT_HOOK, BeforeRenderComponentHook, InContext};
|
||||
|
||||
pub use std::any::Any as AnyComponent;
|
||||
|
||||
pub trait ComponentTrait: AnyComponent + Send + Sync {
|
||||
fn new() -> Self where Self: Sized;
|
||||
|
||||
fn handler(&self) -> &'static str;
|
||||
|
||||
fn name(&self) -> String {
|
||||
util::single_type_name::<Self>().to_owned()
|
||||
}
|
||||
|
||||
fn description(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
fn is_renderable(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn weight(&self) -> isize {
|
||||
0
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn default_render(&self, context: &mut InContext) -> Markup {
|
||||
html! {}
|
||||
}
|
||||
|
||||
fn as_ref_any(&self) -> &dyn AnyComponent;
|
||||
|
||||
fn as_mut_any(&mut self) -> &mut dyn AnyComponent;
|
||||
}
|
||||
|
||||
pub fn component_ref<C: 'static>(component: &dyn ComponentTrait) -> &C {
|
||||
component.as_ref_any().downcast_ref::<C>().unwrap()
|
||||
}
|
||||
|
||||
pub fn component_mut<C: 'static>(component: &mut dyn ComponentTrait) -> &mut C {
|
||||
component.as_mut_any().downcast_mut::<C>().unwrap()
|
||||
}
|
||||
|
||||
pub fn render_component(component: &mut dyn ComponentTrait, context: &mut InContext) -> Markup {
|
||||
// Acciones de los módulos antes de renderizar el componente.
|
||||
run_hooks(
|
||||
BEFORE_RENDER_COMPONENT_HOOK,
|
||||
|hook| hook_ref::<BeforeRenderComponentHook>(&**hook).run(component, context)
|
||||
);
|
||||
|
||||
// Acciones del tema antes de renderizar el componente.
|
||||
context.theme().before_render_component(component, context);
|
||||
|
||||
match component.is_renderable() {
|
||||
true => {
|
||||
match context.theme().render_component(component, context) {
|
||||
Some(html) => html,
|
||||
None => component.default_render(context)
|
||||
}
|
||||
},
|
||||
false => html! {}
|
||||
}
|
||||
}
|
||||
48
pagetop/src/core/component/hook.rs
Normal file
48
pagetop/src/core/component/hook.rs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
use crate::core::hook::{HookTrait, AnyHook};
|
||||
use super::{ComponentTrait, InContext};
|
||||
|
||||
pub const BEFORE_RENDER_COMPONENT_HOOK: &str = "pagetop::hook::before_render_component";
|
||||
|
||||
pub struct BeforeRenderComponentHook {
|
||||
hook: Option<fn(&mut dyn ComponentTrait, &mut InContext)>,
|
||||
weight: isize,
|
||||
}
|
||||
|
||||
impl HookTrait for BeforeRenderComponentHook {
|
||||
fn new() -> Self {
|
||||
BeforeRenderComponentHook {
|
||||
hook: None,
|
||||
weight: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn handler(&self) -> &'static str {
|
||||
BEFORE_RENDER_COMPONENT_HOOK
|
||||
}
|
||||
|
||||
fn weight(&self) -> isize {
|
||||
self.weight
|
||||
}
|
||||
|
||||
fn as_ref_any(&self) -> &dyn AnyHook {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl BeforeRenderComponentHook {
|
||||
pub fn with_hook(mut self, hook: fn(&mut dyn ComponentTrait, &mut InContext)) -> Self {
|
||||
self.hook = Some(hook);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_weight(mut self, weight: isize) -> Self {
|
||||
self.weight = weight;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn run(&self, component: &mut dyn ComponentTrait, context: &mut InContext) {
|
||||
if let Some(hook) = self.hook {
|
||||
hook(component, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
use crate::{Lazy, trace};
|
||||
use crate::core::theme::Theme;
|
||||
use crate::core::module::Module;
|
||||
use crate::core::response::page::PageContainer;
|
||||
use crate::core::server;
|
||||
|
||||
use std::sync::RwLock;
|
||||
use std::collections::HashMap;
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/theme.rs"));
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Temas registrados y tema por defecto.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
pub static THEMES: Lazy<RwLock<Vec<&dyn Theme>>> = Lazy::new(|| {
|
||||
RwLock::new(Vec::new())
|
||||
});
|
||||
|
||||
pub fn themes(cfg: &mut server::web::ServiceConfig) {
|
||||
cfg.service(actix_web_static_files::ResourceFiles::new(
|
||||
"/theme",
|
||||
assets()
|
||||
));
|
||||
|
||||
for t in THEMES.read().unwrap().iter() {
|
||||
t.configure_theme(cfg);
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Módulos registrados.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
pub static MODULES: Lazy<RwLock<Vec<&dyn Module>>> = Lazy::new(|| {
|
||||
RwLock::new(Vec::new())
|
||||
});
|
||||
|
||||
pub fn modules(cfg: &mut server::web::ServiceConfig) {
|
||||
for m in MODULES.read().unwrap().iter() {
|
||||
m.configure_module(cfg);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))]
|
||||
pub fn run_migrations() {
|
||||
trace::info!("Checking migrations.");
|
||||
for m in MODULES.read().unwrap().iter() {
|
||||
m.migrations(
|
||||
&*server::db::DBCONN.read().unwrap()
|
||||
).expect("Failed to run migrations");
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Componentes globales.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
pub static COMPONENTS: Lazy<RwLock<HashMap<&str, PageContainer>>> = Lazy::new(
|
||||
|| { RwLock::new(HashMap::new()) }
|
||||
);
|
||||
14
pagetop/src/core/hook.rs
Normal file
14
pagetop/src/core/hook.rs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
mod definition;
|
||||
pub use definition::{
|
||||
HookTrait,
|
||||
AnyHook,
|
||||
hook_ref,
|
||||
};
|
||||
|
||||
mod holder;
|
||||
pub use holder::HookItem;
|
||||
use holder::HooksHolder;
|
||||
|
||||
mod all;
|
||||
pub use all::run_hooks;
|
||||
pub(crate) use all::add_hook;
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue