Modifica la estructura general del código fuente

Importante actualización que reorganiza el código fuente de PageTop. Usa
Cargo para crear un espacio de trabajo con los diferentes proyectos que
estructuran las funcionalidades de PageTop en módulos interdependientes
que se integran en Drust para construir una solución web para la gestión
de contenidos.
This commit is contained in:
Manuel Cillero 2022-03-13 11:14:33 +01:00
parent 4b5d8ce38a
commit ab0ac11f65
83 changed files with 115 additions and 81 deletions

View file

@ -1,105 +0,0 @@
use crate::prelude::*;
pub struct Block {
renderable: fn() -> bool,
weight : i8,
id : Option<String>,
title : Option<String>,
markup : Vec<Markup>,
template : String,
}
impl PageComponent for Block {
fn prepare() -> Self {
Block {
renderable: always,
weight : 0,
id : None,
title : None,
markup : Vec::new(),
template : "default".to_owned(),
}
}
fn is_renderable(&self) -> bool {
(self.renderable)()
}
fn weight(&self) -> i8 {
self.weight
}
fn default_render(&self, assets: &mut PageAssets) -> Markup {
let id = assets.serial_id(self.name(), self.id());
html! {
div id=(id) class="block" {
@if self.title != None {
h2 class="block-title" { (self.title()) }
}
div class="block-body" {
@for markup in self.markup.iter() {
(*markup)
}
}
}
}
}
}
impl Block {
pub fn markup(markup: Markup) -> Self {
Block::prepare().add_markup(markup)
}
// Block 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
}
pub fn with_title(mut self, title: &str) -> Self {
self.title = util::optional_str(title);
self
}
pub fn add_markup(mut self, markup: Markup) -> Self {
self.markup.push(markup);
self
}
pub fn using_template(mut self, template: &str) -> Self {
self.template = template.to_owned();
self
}
// Block GETTERS.
pub fn id(&self) -> &str {
util::assigned_str(&self.id)
}
pub fn title(&self) -> &str {
util::assigned_str(&self.title)
}
pub fn template(&self) -> &str {
self.template.as_str()
}
}
fn always() -> bool {
true
}

View file

@ -1,75 +0,0 @@
use crate::prelude::*;
pub struct Chunck {
renderable: fn() -> bool,
weight : i8,
markup : Vec<Markup>,
template : String,
}
impl PageComponent for Chunck {
fn prepare() -> Self {
Chunck {
renderable: always,
weight : 0,
markup : Vec::new(),
template : "default".to_owned(),
}
}
fn is_renderable(&self) -> bool {
(self.renderable)()
}
fn weight(&self) -> i8 {
self.weight
}
fn default_render(&self, _: &mut PageAssets) -> Markup {
html! {
@for markup in self.markup.iter() {
(*markup)
}
}
}
}
impl Chunck {
pub fn markup(markup: Markup) -> Self {
Chunck::prepare().add_markup(markup)
}
// Chunck 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 add_markup(mut self, markup: Markup) -> Self {
self.markup.push(markup);
self
}
pub fn using_template(mut self, template: &str) -> Self {
self.template = template.to_owned();
self
}
// Chunck GETTERS.
pub fn template(&self) -> &str {
self.template.as_str()
}
}
fn always() -> bool {
true
}

View file

@ -1,103 +0,0 @@
use crate::prelude::*;
enum ContainerType { Column, Row, Wrapper }
pub struct Container {
renderable: fn() -> bool,
weight : i8,
id : Option<String>,
container : ContainerType,
components: PageContainer,
template : String,
}
impl PageComponent for Container {
fn prepare() -> Self {
Container {
renderable: always,
weight : 0,
id : None,
container : ContainerType::Wrapper,
components: PageContainer::new(),
template : "default".to_owned(),
}
}
fn is_renderable(&self) -> bool {
(self.renderable)()
}
fn weight(&self) -> i8 {
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))
}
}
}
}
impl Container {
pub fn row() -> Self {
let mut grid = Container::prepare();
grid.container = ContainerType::Row;
grid
}
pub fn column() -> Self {
let mut grid = Container::prepare();
grid.container = ContainerType::Column;
grid
}
// Container 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
}
pub fn add(mut self, component: impl PageComponent) -> Self {
self.components.add(component);
self
}
pub fn using_template(mut self, template: &str) -> Self {
self.template = template.to_owned();
self
}
// Container GETTERS.
pub fn id(&self) -> &str {
util::assigned_str(&self.id)
}
pub fn template(&self) -> &str {
self.template.as_str()
}
}
fn always() -> bool {
true
}

View file

@ -1,160 +0,0 @@
use crate::prelude::*;
enum ButtonType {Button, Reset, Submit}
pub struct Button {
renderable : fn() -> bool,
weight : i8,
button_type: ButtonType,
name : Option<String>,
value : Option<String>,
autofocus : Option<String>,
disabled : Option<String>,
template : String,
}
impl PageComponent for Button {
fn prepare() -> Self {
Button {
renderable : always,
weight : 0,
button_type: ButtonType::Button,
name : None,
value : None,
autofocus : None,
disabled : None,
template : "default".to_owned(),
}
}
fn is_renderable(&self) -> bool {
(self.renderable)()
}
fn weight(&self) -> i8 {
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")
};
let id_item = match &self.name {
Some(name) => Some(format!("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]
{
@match &self.value {
Some(value) => (value),
_ => ""
};
}
}
}
}
impl Button {
pub fn button(value: &str) -> Self {
Button::prepare().with_value(value)
}
pub fn reset(value: &str) -> Self {
let mut button = Button::prepare().with_value(value);
button.button_type = ButtonType::Reset;
button
}
pub fn submit(value: &str) -> Self {
let mut button = Button::prepare().with_value(value);
button.button_type = ButtonType::Submit;
button
}
// Button 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_name(mut self, name: &str) -> Self {
self.name = util::valid_id(name);
self
}
pub fn with_value(mut self, value: &str) -> Self {
self.value = util::optional_str(value);
self
}
pub fn autofocus(mut self, toggle: bool) -> Self {
self.autofocus = match toggle {
true => Some("autofocus".to_owned()),
false => None
};
self
}
pub fn disabled(mut self, toggle: bool) -> Self {
self.disabled = match toggle {
true => Some("disabled".to_owned()),
false => None
};
self
}
pub fn using_template(mut self, template: &str) -> Self {
self.template = template.to_owned();
self
}
// Button GETTERS.
pub fn name(&self) -> &str {
util::assigned_str(&self.name)
}
pub fn value(&self) -> &str {
util::assigned_str(&self.value)
}
pub fn has_autofocus(&self) -> bool {
match &self.autofocus {
Some(_) => true,
_ => false
}
}
pub fn is_disabled(&self) -> bool {
match &self.disabled {
Some(_) => true,
_ => false
}
}
pub fn template(&self) -> &str {
self.template.as_str()
}
}
fn always() -> bool {
true
}

View file

@ -1,243 +0,0 @@
use crate::prelude::*;
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>,
template : String,
}
impl PageComponent for Date {
fn prepare() -> Self {
Date {
renderable : always,
weight : 0,
name : None,
value : None,
label : None,
placeholder : None,
autofocus : None,
autocomplete: None,
disabled : None,
readonly : None,
required : None,
help_text : None,
template : "default".to_owned(),
}
}
fn is_renderable(&self) -> bool {
(self.renderable)()
}
fn weight(&self) -> i8 {
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
)
};
html! {
div class=(class_item) {
@if self.label != None {
label class="form-label" for=[&id_item] {
(self.label()) " "
@if self.required != None {
span
class="form-required"
title="Este campo es obligatorio."
{
"*"
} " "
}
}
}
input
type="date"
id=[&id_item]
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())
}
}
}
}
}
}
impl Date {
// Date 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_name(mut self, name: &str) -> Self {
self.name = util::valid_id(name);
self
}
pub fn with_value(mut self, value: &str) -> Self {
self.value = util::optional_str(value);
self
}
pub fn with_label(mut self, label: &str) -> Self {
self.label = util::optional_str(label);
self
}
pub fn with_placeholder(mut self, placeholder: &str) -> Self {
self.placeholder = util::optional_str(placeholder);
self
}
pub fn autofocus(mut self, toggle: bool) -> Self {
self.autofocus = match toggle {
true => Some("autofocus".to_owned()),
false => None
};
self
}
pub fn autocomplete(mut self, toggle: bool) -> Self {
self.autocomplete = match toggle {
true => None,
false => Some("off".to_owned())
};
self
}
pub fn disabled(mut self, toggle: bool) -> Self {
self.disabled = match toggle {
true => Some("disabled".to_owned()),
false => None
};
self
}
pub fn readonly(mut self, toggle: bool) -> Self {
self.readonly = match toggle {
true => Some("readonly".to_owned()),
false => None
};
self
}
pub fn required(mut self, toggle: bool) -> Self {
self.required = match toggle {
true => Some("required".to_owned()),
false => None
};
self
}
pub fn with_help_text(mut self, help_text: &str) -> Self {
self.help_text = util::optional_str(help_text);
self
}
pub fn using_template(mut self, template: &str) -> Self {
self.template = template.to_owned();
self
}
// Date GETTERS.
pub fn name(&self) -> &str {
util::assigned_str(&self.name)
}
pub fn value(&self) -> &str {
util::assigned_str(&self.value)
}
pub fn label(&self) -> &str {
util::assigned_str(&self.label)
}
pub fn placeholder(&self) -> &str {
util::assigned_str(&self.placeholder)
}
pub fn has_autofocus(&self) -> bool {
match &self.autofocus {
Some(_) => true,
_ => false
}
}
pub fn has_autocomplete(&self) -> bool {
match &self.autocomplete {
Some(_) => false,
_ => true
}
}
pub fn is_disabled(&self) -> bool {
match &self.disabled {
Some(_) => true,
_ => false
}
}
pub fn is_readonly(&self) -> bool {
match &self.readonly {
Some(_) => true,
_ => false
}
}
pub fn is_required(&self) -> bool {
match &self.required {
Some(_) => true,
_ => false
}
}
pub fn help_text(&self) -> &str {
util::assigned_str(&self.help_text)
}
pub fn template(&self) -> &str {
self.template.as_str()
}
}
fn always() -> bool {
true
}

View file

@ -1,131 +0,0 @@
use crate::prelude::*;
pub enum FormMethod {Get, Post}
pub struct Form {
renderable: fn() -> bool,
weight : i8,
id : Option<String>,
action : Option<String>,
method : FormMethod,
charset : Option<String>,
elements : PageContainer,
template : String,
}
impl PageComponent for Form {
fn prepare() -> Self {
Form {
renderable: always,
weight : 0,
id : None,
action : None,
method : FormMethod::Post,
charset : Some("UTF-8".to_owned()),
elements : PageContainer::new(),
template : "default".to_owned(),
}
}
fn is_renderable(&self) -> bool {
(self.renderable)()
}
fn weight(&self) -> i8 {
self.weight
}
fn default_render(&self, assets: &mut PageAssets) -> Markup {
let method = match self.method {
FormMethod::Get => None,
FormMethod::Post => Some("post".to_owned())
};
html! {
form
id=[&self.id]
action=[&self.action]
method=[method]
accept-charset=[&self.charset]
{
div {
(self.elements.render(assets))
}
}
}
}
}
impl Form {
// Form 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
}
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 {
self.elements.add(element);
self
}
pub fn using_template(mut self, template: &str) -> Self {
self.template = template.to_owned();
self
}
// Form GETTERS.
pub fn id(&self) -> &str {
util::assigned_str(&self.id)
}
pub fn action(&self) -> &str {
util::assigned_str(&self.action)
}
pub fn method(&self) -> &str {
match &self.method {
FormMethod::Get => "get",
FormMethod::Post => "post"
}
}
pub fn charset(&self) -> &str {
util::assigned_str(&self.charset)
}
pub fn template(&self) -> &str {
self.template.as_str()
}
}
fn always() -> bool {
true
}

View file

@ -1,70 +0,0 @@
use crate::prelude::*;
pub struct Hidden {
weight : i8,
name : Option<String>,
value : Option<String>,
}
impl PageComponent for Hidden {
fn prepare() -> Self {
Hidden {
weight : 0,
name : None,
value : None,
}
}
fn weight(&self) -> i8 {
self.weight
}
fn default_render(&self, _: &mut PageAssets) -> Markup {
let id_item = match &self.name {
Some(name) => Some(format!("value-{}", name)),
_ => None
};
html! {
input
type="hidden"
id=[&id_item]
name=[&self.name]
value=[&self.value];
}
}
}
impl Hidden {
pub fn set(name: &str, value: &str) -> Self {
Hidden::prepare().with_name(name).with_value(value)
}
// Hidden BUILDER.
pub fn with_weight(mut self, weight: i8) -> Self {
self.weight = weight;
self
}
pub fn with_name(mut self, name: &str) -> Self {
self.name = util::valid_id(name);
self
}
pub fn with_value(mut self, value: &str) -> Self {
self.value = util::optional_str(value);
self
}
// Hidden GETTERS.
pub fn name(&self) -> &str {
util::assigned_str(&self.name)
}
pub fn value(&self) -> &str {
util::assigned_str(&self.value)
}
}

View file

@ -1,325 +0,0 @@
use crate::prelude::*;
enum InputType {Email, Password, Search, Telephone, Textfield, Url}
pub struct Input {
renderable : fn() -> bool,
weight : i8,
input_type : InputType,
name : Option<String>,
value : Option<String>,
label : Option<String>,
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>,
template : String,
}
impl PageComponent for Input {
fn prepare() -> Self {
Input {
renderable : always,
weight : 0,
input_type : InputType::Textfield,
name : None,
value : None,
label : None,
size : Some(60),
minlength : None,
maxlength : Some(128),
placeholder : None,
autofocus : None,
autocomplete: None,
disabled : None,
readonly : None,
required : None,
help_text : None,
template : "default".to_owned(),
}
}
fn is_renderable(&self) -> bool {
(self.renderable)()
}
fn weight(&self) -> i8 {
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")
};
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
)
};
html! {
div class=(class_item) {
@if self.label != None {
label class="form-label" for=[&id_item] {
(self.label()) " "
@if self.required != None {
span
class="form-required"
title="Este campo es obligatorio."
{
"*"
} " "
}
}
}
input
type=(input_type)
id=[&id_item]
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())
}
}
}
}
}
}
impl Input {
pub fn textfield() -> Self {
Input::prepare()
}
pub fn password() -> Self {
let mut input = Input::prepare();
input.input_type = InputType::Password;
input
}
pub fn search() -> Self {
let mut input = Input::prepare();
input.input_type = InputType::Search;
input
}
pub fn email() -> Self {
let mut input = Input::prepare();
input.input_type = InputType::Email;
input
}
pub fn telephone() -> Self {
let mut input = Input::prepare();
input.input_type = InputType::Telephone;
input
}
pub fn url() -> Self {
let mut input = Input::prepare();
input.input_type = InputType::Url;
input
}
// Input 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_name(mut self, name: &str) -> Self {
self.name = util::valid_id(name);
self
}
pub fn with_value(mut self, value: &str) -> Self {
self.value = util::optional_str(value);
self
}
pub fn with_label(mut self, label: &str) -> Self {
self.label = util::optional_str(label);
self
}
pub fn with_size(mut self, size: Option<u16>) -> Self {
self.size = size;
self
}
pub fn with_minlength(mut self, minlength: Option<u16>) -> Self {
self.minlength = minlength;
self
}
pub fn with_maxlength(mut self, maxlength: Option<u16>) -> Self {
self.maxlength = maxlength;
self
}
pub fn with_placeholder(mut self, placeholder: &str) -> Self {
self.placeholder = util::optional_str(placeholder);
self
}
pub fn autofocus(mut self, toggle: bool) -> Self {
self.autofocus = match toggle {
true => Some("autofocus".to_owned()),
false => None
};
self
}
pub fn autocomplete(mut self, toggle: bool) -> Self {
self.autocomplete = match toggle {
true => None,
false => Some("off".to_owned())
};
self
}
pub fn disabled(mut self, toggle: bool) -> Self {
self.disabled = match toggle {
true => Some("disabled".to_owned()),
false => None
};
self
}
pub fn readonly(mut self, toggle: bool) -> Self {
self.readonly = match toggle {
true => Some("readonly".to_owned()),
false => None
};
self
}
pub fn required(mut self, toggle: bool) -> Self {
self.required = match toggle {
true => Some("required".to_owned()),
false => None
};
self
}
pub fn with_help_text(mut self, help_text: &str) -> Self {
self.help_text = util::optional_str(help_text);
self
}
pub fn using_template(mut self, template: &str) -> Self {
self.template = template.to_owned();
self
}
// Input GETTERS.
pub fn name(&self) -> &str {
util::assigned_str(&self.name)
}
pub fn value(&self) -> &str {
util::assigned_str(&self.value)
}
pub fn label(&self) -> &str {
util::assigned_str(&self.label)
}
pub fn size(&self) -> Option<u16> {
self.size
}
pub fn minlength(&self) -> Option<u16> {
self.minlength
}
pub fn maxlength(&self) -> Option<u16> {
self.maxlength
}
pub fn placeholder(&self) -> &str {
util::assigned_str(&self.placeholder)
}
pub fn has_autofocus(&self) -> bool {
match &self.autofocus {
Some(_) => true,
_ => false
}
}
pub fn has_autocomplete(&self) -> bool {
match &self.autocomplete {
Some(_) => false,
_ => true
}
}
pub fn is_disabled(&self) -> bool {
match &self.disabled {
Some(_) => true,
_ => false
}
}
pub fn is_readonly(&self) -> bool {
match &self.readonly {
Some(_) => true,
_ => false
}
}
pub fn is_required(&self) -> bool {
match &self.required {
Some(_) => true,
_ => false
}
}
pub fn help_text(&self) -> &str {
util::assigned_str(&self.help_text)
}
pub fn template(&self) -> &str {
self.template.as_str()
}
}
fn always() -> bool {
true
}

View file

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

View file

@ -1,252 +0,0 @@
use crate::prelude::*;
enum MenuItemType {
Label(String),
Link(String, String),
LinkBlank(String, String),
Markup(Markup),
Separator,
Submenu(String, Menu),
}
// -----------------------------------------------------------------------------
// MenuItem.
// -----------------------------------------------------------------------------
pub struct MenuItem {
renderable: fn() -> bool,
weight : i8,
item_type : Option<MenuItemType>,
}
impl PageComponent for MenuItem {
fn prepare() -> Self {
MenuItem {
renderable: always,
weight : 0,
item_type : None,
}
}
fn is_renderable(&self) -> bool {
(self.renderable)()
}
fn weight(&self) -> i8 {
self.weight
}
fn default_render(&self, assets: &mut PageAssets) -> Markup {
match &self.item_type {
Some(MenuItemType::Label(label)) => html! {
li class="label" { a href="#" { (label) } }
},
Some(MenuItemType::Link(label, path)) => html! {
li class="link" { a href=(path) { (label) } }
},
Some(MenuItemType::LinkBlank(label, path)) => html! {
li class="link_blank" {
a href=(path) target="_blank" { (label) }
}
},
Some(MenuItemType::Markup(markup)) => html! {
li class="markup" { (*markup) }
},
Some(MenuItemType::Submenu(label, menu)) => html! {
li class="submenu" {
a href="#" { (label) }
ul {
(menu.render_items(assets))
}
}
},
Some(MenuItemType::Separator) => html! {
li class="separator" { }
},
None => html! {}
}
}
}
impl MenuItem {
pub fn label(label: &str) -> Self {
MenuItem {
renderable: always,
weight : 0,
item_type : Some(MenuItemType::Label(label.to_owned())),
}
}
pub fn link(label: &str, path: &str) -> Self {
MenuItem {
renderable: always,
weight : 0,
item_type : Some(MenuItemType::Link(
label.to_owned(),
path.to_owned(),
)),
}
}
pub fn link_blank(label: &str, path: &str) -> Self {
MenuItem {
renderable: always,
weight : 0,
item_type : Some(MenuItemType::LinkBlank(
label.to_owned(),
path.to_owned(),
)),
}
}
pub fn markup(markup: Markup) -> Self {
MenuItem {
renderable: always,
weight : 0,
item_type : Some(MenuItemType::Markup(markup)),
}
}
pub fn separator() -> Self {
MenuItem {
renderable: always,
weight : 0,
item_type : Some(MenuItemType::Separator),
}
}
pub fn submenu(label: &str, menu: Menu) -> Self {
MenuItem {
renderable: always,
weight : 0,
item_type : Some(MenuItemType::Submenu(
label.to_owned(),
menu
)),
}
}
// MenuItem 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
}
}
// -----------------------------------------------------------------------------
// Menu.
// -----------------------------------------------------------------------------
pub struct Menu {
renderable: fn() -> bool,
weight : i8,
id : Option<String>,
items : PageContainer,
template : String,
}
impl PageComponent for Menu {
fn prepare() -> Self {
Menu {
renderable: always,
weight : 0,
id : None,
items : PageContainer::new(),
template : "default".to_owned(),
}
}
fn is_renderable(&self) -> bool {
(self.renderable)()
}
fn weight(&self) -> i8 {
self.weight
}
fn default_render(&self, assets: &mut PageAssets) -> Markup {
assets
.add_stylesheet(StyleSheet::source(
"/theme/menu/css/menu.css?ver=1.1.1"
))
.add_stylesheet(StyleSheet::source(
"/theme/menu/css/menu-clean.css?ver=1.1.1"
))
.add_javascript(JavaScript::source(
"/theme/menu/js/menu.min.js?ver=1.1.1"
))
.add_jquery();
let id = assets.serial_id(self.name(), self.id());
html! {
ul id=(id) class="sm sm-clean" {
(self.render_items(assets))
}
script type="text/javascript" defer {
"jQuery(function(){jQuery('#" (id) "').smartmenus({"
"hideTimeout: 0,"
"showTimeout: 80,"
"});});"
}
}
}
}
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
}
pub fn add(mut self, item: MenuItem) -> Self {
self.items.add(item);
self
}
pub fn using_template(mut self, template: &str) -> Self {
self.template = template.to_owned();
self
}
// Menu GETTERS.
pub fn id(&self) -> &str {
util::assigned_str(&self.id)
}
pub fn template(&self) -> &str {
self.template.as_str()
}
// Menu EXTRAS.
pub fn render_items(&self, assets: &mut PageAssets) -> Markup {
html! { (self.items.render(assets)) }
}
}
fn always() -> bool {
true
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,3 +0,0 @@
pub mod admin;
pub mod homepage;
pub mod user;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,5 +0,0 @@
e404-description = Oops! Page Not Found
e404-message = The page you are looking for may have been removed, had its name changed, or is temporarily unavailable.
e500-description = Oops! Unexpected Error
e500-message = We're having an issue. Please report this error to an administrator.
back-homepage = Back to homepage

View file

@ -1,5 +0,0 @@
e404-description = ¡Vaya! Página No Encontrada
e404-message = La página que está buscando puede haber sido eliminada, cambiada de nombre o no está disponible temporalmente.
e500-description = ¡Vaya! Error Inesperado
e500-message = Está ocurriendo una incidencia. Por favor, informe de este error a un administrador.
back-homepage = Volver al inicio

View file

@ -1,86 +0,0 @@
use crate::prelude::*;
include!(concat!(env!("OUT_DIR"), "/bootsier.rs"));
localize!("en-US", "src/base/theme/bootsier/locales");
pub struct BootsierTheme;
impl Theme for BootsierTheme {
fn name(&self) -> &'static str {
"bootsier"
}
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 before_render_page(&self, page: &mut Page) {
page.assets()
.with_favicon(
Favicon::new()
.with_icon("/bootsier/favicon.png")
)
.add_stylesheet(
StyleSheet::source(
"/bootsier/css/bootstrap.min.css?ver=5.1.3"
)
.with_weight(-99)
)
.add_javascript(
JavaScript::source(
"/bootsier/js/bootstrap.bundle.min.js?ver=5.1.3"
)
.with_weight(-99)
)
.add_jquery();
}
fn render_error_page(&self, mut s: server::http::StatusCode) -> server::Result<Markup> {
let mut description = "e500-description";
let mut message = "e500-description";
match s {
server::http::StatusCode::NOT_FOUND => {
description = "e404-description";
message = "e404-message";
},
_ => {
s = server::http::StatusCode::INTERNAL_SERVER_ERROR;
}
}
Page::prepare()
.with_title(format!("Error {}", s.as_str()).as_str())
.add_to("content", Chunck::markup(html! {
div class="jumbotron" {
div class="media" {
img
src="/bootsier/images/caution.png"
class="mr-4"
style="width: 20%; max-width: 188px"
alt="Caution!";
div class="media-body" {
h1 class="display-4" { (s) }
p class="lead" { (l(description)) }
hr class="my-4";
p { (l(message)) }
a
class="btn btn-primary btn-lg"
href="/"
role="button"
{
(l("back-homepage"))
}
}
}
}
}))
.render()
}
}

View file

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

View file

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

View file

@ -1,146 +0,0 @@
use crate::Lazy;
use config_rs::{Config, File};
use serde::Deserialize;
use std::env;
/// Nombre del directorio donde se encuentra la configuración.
const CONFIG_DIR: &'static str = "config";
/// Al arrancar la aplicación, carga los valores originales "clave = valor" de
/// los archivos de configuración. Con [`config_map`] se asignarán los ajustes
/// globales ([`SETTINGS`]); y se podrán asignar los ajustes específicos de la
/// aplicación, o también de un tema, módulo o componente.
pub static CONFIG: Lazy<Config> = Lazy::new(|| {
// Establece el modo de ejecución según el valor de la variable de entorno
// PAGETOP_RUN_MODE. Asume "default" por defecto.
let run_mode = env::var("PAGETOP_RUN_MODE").unwrap_or("default".into());
// Inicializa los ajustes.
let mut settings = Config::default();
// Combina los archivos de configuración y asigna el modo de ejecución.
settings
.merge(
File::with_name(
&format!("{}/{}.toml", CONFIG_DIR, "common")
).required(false)).unwrap()
.merge(
File::with_name(
&format!("{}/{}.toml", CONFIG_DIR, run_mode)
).required(false)).unwrap()
.merge(
File::with_name(
&format!("{}/{}.toml", CONFIG_DIR, "local")
).required(false)).unwrap()
.set("app.run_mode", run_mode).unwrap();
settings
});
#[macro_export]
/// Asigna los ajustes específicos de la aplicación, o de un tema, módulo o
/// componente, en una estructura similar a [`SETTINGS`] con tipos de variables
/// seguros. Produce un *panic!* en caso de asignaciones no válidas.
macro_rules! config_map {
(
$COMM:expr,
$CONF:ident,
$TYPE:tt
$(, $key:expr => $value:expr)*
) => {
$crate::doc_comment! {
concat!($COMM),
pub static $CONF: $crate::Lazy<$TYPE> = $crate::Lazy::new(|| {
let mut settings = $crate::config::CONFIG.clone();
$(
settings.set_default($key, $value).unwrap();
)*
match settings.try_into() {
Ok(c) => c,
Err(e) => panic!("Error parsing settings: {}", e),
}
});
}
};
}
#[derive(Debug, Deserialize)]
pub struct App {
pub name : String,
pub description : String,
pub theme : String,
pub language : String,
pub direction : String,
pub startup_banner: String,
pub run_mode : String,
}
#[derive(Debug, Deserialize)]
pub struct Log {
pub tracing : String,
pub rolling : String,
pub path : String,
pub prefix : String,
pub format : String,
}
#[derive(Debug, Deserialize)]
pub struct Database {
pub db_name : String,
pub db_user : String,
pub db_pass : String,
pub db_host : String,
pub db_port : u16,
pub max_pool_size : u32,
}
#[derive(Debug, Deserialize)]
pub struct Webserver {
pub bind_address : String,
pub bind_port : u16,
}
#[derive(Debug, Deserialize)]
pub struct Settings {
pub app : App,
pub log : Log,
pub database : Database,
pub webserver : Webserver,
}
config_map!(r#"
Ajustes globales y valores predeterminados para las secciones *\[app\]*,
*\[log\]* y *\[webserver\]* de PageTop.
"#,
SETTINGS, Settings,
// [app]
"app.name" => "PageTop Application",
"app.description" => "Developed with the amazing PageTop framework.",
"app.theme" => "Bootsier",
"app.language" => "en-US",
"app.direction" => "ltr",
"app.startup_banner" => "Small",
// [log]
"log.tracing" => "Info",
"log.rolling" => "Daily",
"log.path" => "log",
"log.prefix" => "tracing.log",
"log.format" => "json",
// [database]
"database.db_name" => "dbname",
"database.db_user" => "dbuser",
"database.db_pass" => "dbpass",
"database.db_host" => "localhost",
"database.db_port" => 0,
"database.max_pool_size" => 5,
// [webserver]
"webserver.bind_address" => "localhost",
"webserver.bind_port" => 8088
);

View file

@ -1,57 +0,0 @@
use crate::{Lazy, db};
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);
}
}
pub fn migrations(dbconn: &db::DbConn) {
for m in MODULES.read().unwrap().iter() {
m.migrations(dbconn).expect("Failed to run migrations");
}
}
// -----------------------------------------------------------------------------
// Componentes globales.
// -----------------------------------------------------------------------------
pub static COMPONENTS: Lazy<RwLock<HashMap<&str, PageContainer>>> = Lazy::new(
|| { RwLock::new(HashMap::new()) }
);

View file

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

View file

@ -1,22 +0,0 @@
use crate::db;
use crate::core::server;
/// Los módulos deben implementar este *trait*.
pub trait Module: Send + Sync {
fn name(&self) -> &'static str;
fn fullname(&self) -> String;
fn description(&self) -> Option<String> {
None
}
#[allow(unused_variables)]
fn configure_module(&self, cfg: &mut server::web::ServiceConfig) {
}
#[allow(unused_variables)]
fn migrations(&self, dbconn: &db::DbConn) -> Result<(), db::DbErr> {
Ok(())
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,127 +0,0 @@
use crate::{Lazy, base, db, locale, trace};
use crate::config::SETTINGS;
use crate::core::{Server, global, server};
use crate::core::theme::register_theme;
use crate::core::module::register_module;
use std::io::Error;
use actix_web::middleware::normalize::{NormalizePath, TrailingSlash};
pub struct Application {
server: Server,
}
impl Application {
pub async fn build(bootstrap: Option<fn()>) -> Result<Self, Error> {
// Imprime rótulo (opcional) de bienvenida.
if SETTINGS.app.startup_banner.to_lowercase() != "off" {
let figfont = figlet_rs::FIGfont::from_content(
match SETTINGS.app.startup_banner.to_lowercase().as_str() {
"slant" => include_str!("figfonts/slant.flf"),
"small" => include_str!("figfonts/small.flf"),
"speed" => include_str!("figfonts/speed.flf"),
"starwars" => include_str!("figfonts/starwars.flf"),
_ => {
println!(
"FIGfont \"{}\" not found for banner. {}. {}.",
SETTINGS.app.startup_banner,
"Using \"Small\"",
"Check the settings file",
);
include_str!("figfonts/small.flf")
}
}
).unwrap();
println!("\n{} {}\n\n Powered by PageTop {}\n",
figfont.convert(&SETTINGS.app.name).unwrap(),
&SETTINGS.app.description,
env!("CARGO_PKG_VERSION")
);
}
// Inicia registro de trazas y eventos.
Lazy::force(&server::tracing::TRACING);
// Valida el identificador de idioma.
Lazy::force(&locale::LANGID);
// Inicializa la conexión con la base de datos.
trace::info!(
"Connecting to database \"{}\" with a pool of {} connections.",
&SETTINGS.database.db_name,
&SETTINGS.database.max_pool_size
);
#[cfg(feature = "mysql")]
let db_type = "mysql";
#[cfg(feature = "postgres")]
let db_type = "postgres";
// https://github.com/launchbadge/sqlx/issues/1624
let mut db_uri = db::DbUri::parse(format!(
"{}://{}/{}",
db_type,
&SETTINGS.database.db_host,
&SETTINGS.database.db_name
).as_str()).unwrap();
db_uri.set_username(&SETTINGS.database.db_user.as_str()).unwrap();
db_uri.set_password(Some(&SETTINGS.database.db_pass.as_str())).unwrap();
if SETTINGS.database.db_port != 0 {
db_uri.set_port(Some(SETTINGS.database.db_port)).unwrap();
}
let mut db_options = sea_orm::ConnectOptions::new(db_uri.to_string());
db_options.max_connections(SETTINGS.database.max_pool_size);
let dbconn = sea_orm::Database::connect::<sea_orm::ConnectOptions>(
db_options.into()
)
.await
.expect("Failed to connect to database");
// Registra los temas predefinidos.
register_theme(&base::theme::aliner::AlinerTheme);
register_theme(&base::theme::minimal::MinimalTheme);
register_theme(&base::theme::bootsier::BootsierTheme);
// Registra los módulos predeterminados.
register_module(&base::module::admin::AdminModule);
register_module(&base::module::user::UserModule);
// Ejecuta la función de inicio de la aplicación.
if bootstrap != None {
trace::info!("Calling application bootstrap.");
let _ = &(bootstrap.unwrap())();
}
// Registra el módulo para la página de inicio de PageTop.
// Al ser el último, puede sobrecargarse con la función de inicio.
register_module(&base::module::homepage::HomepageModule);
// Run migrations.
trace::info!("Running migrations.");
global::migrations(&dbconn);
// Prepara el servidor web.
let server = server::HttpServer::new(move || {
server::App::new()
.wrap(tracing_actix_web::TracingLogger)
.wrap(NormalizePath::new(TrailingSlash::Trim))
.data(dbconn.clone())
.configure(&global::themes)
.configure(&global::modules)
})
.bind(format!("{}:{}",
&SETTINGS.webserver.bind_address,
&SETTINGS.webserver.bind_port
))?
.run();
Ok(Self { server })
}
pub fn run(self) -> Result<Server, Error> {
Ok(self.server)
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,719 +0,0 @@
flf2a$ 7 6 22 15 4
starwars.flf by Ryan Youck (youck@cs.uregina.ca) Dec 25/1994
I am not responsible for use of this font
Based on Big.flf by Glenn Chappell
$ $@
$ $@
$ $@
$ $@
$ $@
$ $@
$ $@@
__ $@
| |$@
| |$@
| |$@
|__|$@
(__)$@
$@@
_ _ @
( | )@
V V @
$ @
$ @
$ @
@@
_ _ @
_| || |_$@
|_ __ _|@
_| || |_ @
|_ __ _|@
|_||_| $@
@@
__,--,_.@
/ |@
| (----`@
\ \ $@
.----) | $@
|_ __/ $@
'--' $@@
_ ___$ @
/ \ / /$ @
( o ) / / $ @
\_/ / / _$ @
/ / / \ @
/ / ( o )@
/__/ \_/ @@
@
___ @
( _ ) $@
/ _ \/\@
| (_> <@
\___/\/@
$@@
__ @
(_ )@
|/ @
$ @
$ @
$ @
@@
___@
/ /@
| |$@
| |$@
| |$@
| |$@
\__\@@
___ @
\ \ @
| |@
| |@
| |@
| |@
/__/ @@
_ @
/\| |/\ @
\ ` ' /$@
|_ _|@
/ , . \$@
\/|_|\/ @
@@
@
_ @
_| |_$@
|_ _|@
|_| $@
$ @
@@
@
@
$ @
$ @
__ @
(_ )@
|/ @@
@
@
______ @
|______|@
$ @
$ @
@@
@
@
@
$ @
__ @
(__)@
@@
___@
/ /@
/ / @
/ /$ @
/ /$ @
/__/$ @
@@
___ $@
/ _ \ $@
| | | |$@
| | | |$@
| |_| |$@
\___/ $@
$@@
__ $@
/_ |$@
| |$@
| |$@
| |$@
|_|$@
$@@
___ $@
|__ \ $@
$) |$@
/ / $@
/ /_ $@
|____|$@
$@@
____ $@
|___ \ $@
__) |$@
|__ < $@
___) |$@
|____/ $@
$@@
_ _ $@
| || | $@
| || |_ $@
|__ _|$@
| | $@
|_| $@
$@@
_____ $@
| ____|$@
| |__ $@
|___ \ $@
___) |$@
|____/ $@
$@@
__ $@
/ / $@
/ /_ $@
| '_ \ $@
| (_) |$@
\___/ $@
$@@
______ $@
|____ |$@
$/ / $@
/ / $@
/ / $@
/_/ $@
$@@
___ $@
/ _ \ $@
| (_) |$@
> _ < $@
| (_) |$@
\___/ $@
$@@
___ $@
/ _ \ $@
| (_) |$@
\__, |$@
/ / $@
/_/ $@
$@@
@
_ @
(_)@
$ @
_ @
(_)@
@@
@
_ @
(_)@
$ @
_ @
( )@
|/ @@
___@
/ /@
/ /$@
< <$ @
\ \$@
\__\@
@@
@
______ @
|______|@
______ @
|______|@
@
@@
___ @
\ \$ @
\ \ @
> >@
/ / @
/__/$ @
@@
______ $@
| \ $@
`----) |$@
/ / $@
|__| $@
__ $@
(__) $@@
____ @
/ __ \ @
/ / _` |@
| | (_| |@
\ \__,_|@
\____/ @
@@
___ $ @
/ \ $ @
/ ^ \$ @
/ /_\ \$ @
/ _____ \$ @
/__/ \__\$@
$@@
.______ $@
| _ \ $@
| |_) |$@
| _ < $@
| |_) |$@
|______/ $@
$@@
______$@
/ |@
| ,----'@
| | $@
| `----.@
\______|@
$@@
_______ $@
| \$@
| .--. |@
| | | |@
| '--' |@
|_______/$@
$@@
_______ @
| ____|@
| |__ $@
| __| $@
| |____ @
|_______|@
@@
_______ @
| ____|@
| |__ $@
| __| $@
| | $ @
|__| @
@@
_______ @
/ _____|@
| | __ $@
| | |_ |$@
| |__| |$@
\______|$@
$@@
__ __ $@
| | | |$@
| |__| |$@
| __ |$@
| | | |$@
|__| |__|$@
$@@
__ $@
| |$@
| |$@
| |$@
| |$@
|__|$@
$@@
__ $@
| |$@
| |$@
.--. | |$@
| `--' |$@
\______/ $@
$@@
__ ___$@
| |/ /$@
| ' / $@
| < $@
| . \ $@
|__|\__\$@
$@@
__ $@
| | $@
| | $@
| | $@
| `----.@
|_______|@
$@@
.___ ___.$@
| \/ |$@
| \ / |$@
| |\/| |$@
| | | |$@
|__| |__|$@
$@@
.__ __.$@
| \ | |$@
| \| |$@
| . ` |$@
| |\ |$@
|__| \__|$@
$@@
______ $@
/ __ \ $@
| | | |$@
| | | |$@
| `--' |$@
\______/ $@
$@@
.______ $@
| _ \ $@
| |_) |$@
| ___/ $@
| | $ @
| _| $ @
$ @@
______ $ @
/ __ \ $ @
| | | | $ @
| | | | $ @
| `--' '--. @
\_____\_____\@
$ @@
.______ $ @
| _ \ $ @
| |_) | $ @
| / $ @
| |\ \----.@
| _| `._____|@
$@@
_______.@
/ |@
| (----`@
\ \ $@
.----) | $@
|_______/ $@
$@@
.___________.@
| |@
`---| |----`@
| | $ @
| | $ @
|__| $ @
$ @@
__ __ $@
| | | |$@
| | | |$@
| | | |$@
| `--' |$@
\______/ $@
$@@
____ ____$@
\ \ / /$@
\ \/ /$ @
\ /$ @
\ /$ @
\__/$ @
$ @@
____ __ ____$@
\ \ / \ / /$@
\ \/ \/ /$ @
\ /$ @
\ /\ /$ @
\__/ \__/$ @
$ @@
___ ___$@
\ \ / /$@
\ V / $@
> < $@
/ . \ $@
/__/ \__\$@
$@@
____ ____$@
\ \ / /$@
\ \/ /$ @
\_ _/$ @
| |$ @
|__|$ @
$ @@
________ $@
| / $@
`---/ / $@
/ / $@
/ /----.@
/________|@
$@@
____ @
| |@
| |-`@
| | $@
| | $@
| |-.@
|____|@@
___ @
\ \ $ @
\ \$ @
\ \$ @
\ \$@
\__\@
@@
____ @
| |@
`-| |@
| |@
| |@
.-| |@
|____|@@
___ @
/ \ @
/--^--\@
$@
$@
$@
$@@
@
@
@
$ @
$ @
______ @
|______|@@
__ @
( _)@
\| @
$ @
$ @
$ @
@@
___ $ @
/ \ $ @
/ ^ \$ @
/ /_\ \$ @
/ _____ \$ @
/__/ \__\$@
$@@
.______ $@
| _ \ $@
| |_) |$@
| _ < $@
| |_) |$@
|______/ $@
$@@
______$@
/ |@
| ,----'@
| | $@
| `----.@
\______|@
$@@
_______ $@
| \$@
| .--. |@
| | | |@
| '--' |@
|_______/$@
$@@
_______ @
| ____|@
| |__ $@
| __| $@
| |____ @
|_______|@
@@
_______ @
| ____|@
| |__ $@
| __| $@
| | $ @
|__| @
@@
_______ @
/ _____|@
| | __ $@
| | |_ |$@
| |__| |$@
\______|$@
$@@
__ __ $@
| | | |$@
| |__| |$@
| __ |$@
| | | |$@
|__| |__|$@
$@@
__ $@
| |$@
| |$@
| |$@
| |$@
|__|$@
$@@
__ $@
| |$@
| |$@
.--. | |$@
| `--' |$@
\______/ $@
$@@
__ ___$@
| |/ /$@
| ' / $@
| < $@
| . \ $@
|__|\__\$@
$@@
__ $@
| | $@
| | $@
| | $@
| `----.@
|_______|@
$@@
.___ ___.$@
| \/ |$@
| \ / |$@
| |\/| |$@
| | | |$@
|__| |__|$@
$@@
.__ __.$@
| \ | |$@
| \| |$@
| . ` |$@
| |\ |$@
|__| \__|$@
$@@
______ $@
/ __ \ $@
| | | |$@
| | | |$@
| `--' |$@
\______/ $@
$@@
.______ $@
| _ \ $@
| |_) |$@
| ___/ $@
| | $ @
| _| $ @
$ @@
______ $ @
/ __ \ $ @
| | | | $ @
| | | | $ @
| `--' '--. @
\_____\_____\@
$ @@
.______ $ @
| _ \ $ @
| |_) | $ @
| / $ @
| |\ \----.@
| _| `._____|@
$@@
_______.@
/ |@
| (----`@
\ \ $@
.----) | $@
|_______/ $@
$@@
.___________.@
| |@
`---| |----`@
| | $ @
| | $ @
|__| $ @
$ @@
__ __ $@
| | | |$@
| | | |$@
| | | |$@
| `--' |$@
\______/ $@
$@@
____ ____$@
\ \ / /$@
\ \/ /$ @
\ /$ @
\ /$ @
\__/$ @
$ @@
____ __ ____$@
\ \ / \ / /$@
\ \/ \/ /$ @
\ /$ @
\ /\ /$ @
\__/ \__/$ @
$ @@
___ ___$@
\ \ / /$@
\ V / $@
> < $@
/ . \ $@
/__/ \__\$@
$@@
____ ____$@
\ \ / /$@
\ \/ /$ @
\_ _/$ @
| |$ @
|__|$ @
$ @@
________ $@
| / $@
`---/ / $@
/ / $@
/ /----.@
/________|@
$@@
___@
/ /@
| |$@
/ /$ @
\ \$ @
| |$@
\__\@@
__ $@
| |$@
| |$@
| |$@
| |$@
| |$@
|__|$@@
___ @
\ \$ @
| | @
\ \@
/ /@
| | @
/__/$ @@
__ _ @
/ \/ |@
|_/\__/ @
$ @
$ @
$ @
@@
_ _ @
(_)_(_) @
/ \ @
/ _ \ @
/ ___ \ @
/_/ \_\@
@@
_ _ @
(_)_(_)@
/ _ \ @
| | | |@
| |_| |@
\___/ @
@@
_ _ @
(_) (_)@
| | | |@
| | | |@
| |_| |@
\___/ @
@@
_ _ @
(_) (_)@
__ _ @
/ _` |@
| (_| |@
\__,_|@
@@
_ _ @
(_) (_)@
___ @
/ _ \ @
| (_) |@
\___/ @
@@
_ _ @
(_) (_)@
_ _ @
| | | |@
| |_| |@
\__,_|@
@@
___ @
/ _ \ @
| | ) |@
| |< < @
| | ) |@
| ||_/ @
|_| @@

View file

@ -1,8 +0,0 @@
pub use actix_web::{
App, HttpRequest, HttpResponse, HttpServer, Responder, Result, http, web
};
mod tracing;
mod app;
pub use app::Application;

View file

@ -1,70 +0,0 @@
use crate::Lazy;
use crate::config::SETTINGS;
use tracing_appender::non_blocking::WorkerGuard;
use tracing_subscriber::EnvFilter;
/// Registro de trazas y eventos de la aplicación.
///
/// Para aumentar el rendimiento, un subproceso dedicado utiliza un sistema de
/// escritura sin bloqueo (*non-blocking writer*) que actúa periódicamente en
/// vez de enviar cada traza o evento al instante. Si el programa termina
/// abruptamente (por ejemplo, por un `panic!` o un `std::process::exit`), es
/// posible que algunas trazas o eventos no se envíen.
///
/// Puesto que las trazas o eventos registrados poco antes de la caída de una
/// aplicación suelen ser importantes para diagnosticar la causa del fallo, con
/// `Lazy<WorkerGuard>` se garantiza que todos los registros almacenados se
/// enviarán antes de terminar la ejecución.
pub static TRACING: Lazy<WorkerGuard> = Lazy::new(|| {
let env_filter = EnvFilter::try_new(&SETTINGS.log.tracing)
.unwrap_or(EnvFilter::new("Info"));
let rolling = SETTINGS.log.rolling.to_lowercase();
let (non_blocking, guard) = match rolling.as_str() {
"stdout" => tracing_appender::non_blocking(
std::io::stdout()
),
_ => tracing_appender::non_blocking({
let path = &SETTINGS.log.path;
let prefix = &SETTINGS.log.prefix;
match rolling.as_str() {
"daily" => tracing_appender::rolling::daily(path, prefix),
"hourly" => tracing_appender::rolling::hourly(path, prefix),
"minutely" => tracing_appender::rolling::minutely(path, prefix),
"endless" => tracing_appender::rolling::never(path, prefix),
_ => {
println!(
"Rolling value \"{}\" not valid. {}. {}.",
SETTINGS.log.rolling,
"Using \"daily\"",
"Check the settings file",
);
tracing_appender::rolling::daily(path, prefix)
}
}
})
};
let subscriber = tracing_subscriber::fmt()
.with_env_filter(env_filter)
.with_writer(non_blocking)
.with_ansi(rolling.as_str() == "stdout");
match SETTINGS.log.format.to_lowercase().as_str() {
"json" => subscriber.json().init(),
"full" => subscriber.init(),
"compact" => subscriber.compact().init(),
"pretty" => subscriber.pretty().init(),
_ => {
println!(
"Tracing format \"{}\" not valid. {}. {}.",
SETTINGS.log.format,
"Using \"Full\"",
"Check the settings file",
);
subscriber.init();
}
}
guard
});

View file

@ -1,106 +0,0 @@
use crate::config::SETTINGS;
use crate::core::server;
use crate::core::theme::{Markup, html};
use crate::core::response::page::{Page, PageAssets, PageComponent};
use crate::base::component::Chunck;
/// Los temas deben implementar este "trait".
pub trait Theme: Send + Sync {
fn name(&self) -> &'static str;
fn fullname(&self) -> String;
fn description(&self) -> Option<String> {
None
}
#[allow(unused_variables)]
fn configure_theme(&self, cfg: &mut server::web::ServiceConfig) {
}
#[allow(unused_variables)]
fn before_render_page(&self, page: &mut Page) {
}
fn render_page_head(&self, page: &mut Page) -> Markup {
let title = page.title();
let title = if title.is_empty() {
SETTINGS.app.name.to_owned()
} else {
[SETTINGS.app.name.to_string(), title.to_string()].join(" | ")
};
let description = page.description();
let viewport = "width=device-width, initial-scale=1, shrink-to-fit=no";
html! {
head {
meta charset="utf-8";
title { (title) }
@if !description.is_empty() {
meta name="description" content=(description);
}
meta http-equiv="X-UA-Compatible" content="IE=edge";
meta name="viewport" content=(viewport);
(page.assets().render())
}
}
}
fn render_page_body(&self, page: &mut Page) -> Markup {
html! {
body class=(page.body_classes()) {
@match page.template() {
"admin" => {
@for region in &["top-menu", "side-menu", "content"] {
#(region) {
(page.render_region(region))
}
}
},
_ => {
#content {
(page.render_region("content"))
}
}
}
}
}
}
#[allow(unused_variables)]
fn render_component(
&self,
component: &dyn PageComponent,
assets: &mut PageAssets
) -> Option<Markup> {
None
/*
Cómo usarlo:
match component.name() {
"block" => {
let block = component.downcast_mut::<Block>().unwrap();
match block.template() {
"default" => Some(block_default(block)),
_ => None
}
},
_ => None
}
*/
}
fn render_error_page(&self, s: server::http::StatusCode) -> server::Result<Markup> {
Page::prepare()
.with_title(format!("Error {}", s.as_str()).as_str())
.add_to("content", Chunck::markup(html! {
div {
h1 { (s) }
}
}))
.render()
}
}

View file

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

View file

@ -1,14 +0,0 @@
pub use url::Url as DbUri;
pub use sea_orm::{
DbErr,
DatabaseConnection as DbConn,
};
pub mod entity {
pub use sea_orm::entity::prelude::*;
}
pub mod migration {
pub use sea_schema::migration::prelude::*;
}

View file

@ -1,21 +0,0 @@
// Global.
pub use doc_comment::doc_comment;
pub use once_cell::sync::Lazy;
pub use futures::executor::block_on as run_now;
// -----------------------------------------------------------------------------
// APIs públicas.
// -----------------------------------------------------------------------------
pub mod config; // Gestión de la configuración.
pub mod trace; // Registro de trazas y eventos de la aplicación.
pub mod locale; // Localización.
pub mod db; // Acceso a la base de datos.
pub mod core; // Servidor web y APIs para Temas, Módulos y Respuestas web.
pub mod base; // Temas, Módulos y Componentes base.
pub mod util; // Macros y funciones útiles.
pub mod prelude; // Re-exporta recursos comunes.
pub use crate::core::server::Application;

View file

@ -1,69 +0,0 @@
pub use fluent_templates::{static_loader as static_locale, Loader as Locale};
pub use fluent_templates;
pub use fluent_templates::fluent_bundle::FluentValue;
use crate::{Lazy, trace};
use crate::config::SETTINGS;
use unic_langid::LanguageIdentifier;
/// Almacena el Identificador de Idioma Unicode ([Unicode Language Identifier]
/// (https://unicode.org/reports/tr35/tr35.html#Unicode_language_identifier)) de
/// la aplicación, obtenido de `SETTINGS.app.language`.
pub static LANGID: Lazy<LanguageIdentifier> = Lazy::new(|| {
match SETTINGS.app.language.parse() {
Ok(language) => language,
Err(_) => {
trace::warn!(
"Failed to parse language \"{}\". {}. {}. {}.",
SETTINGS.app.language,
"Unicode Language Identifier not recognized",
"Using \"en-US\"",
"Check the settings file",
);
"en-US".parse().unwrap()
}
}
});
#[macro_export]
/// Permite integrar fácilmente localización en temas, módulos y componentes.
macro_rules! localize {
( $DEF_LANGID:literal, $locales:literal $(, $core_locales:literal)? ) => {
use $crate::locale::*;
static_locale! {
static LOCALES = {
locales: $locales,
$( core_locales: $core_locales, )?
fallback_language: $DEF_LANGID,
// Elimina las marcas Unicode que delimitan los argumentos.
customise: |bundle| bundle.set_use_isolating(false),
};
}
#[allow(dead_code)]
fn l(key: &str) -> String {
LOCALES.lookup(&LANGID, key)
}
#[allow(dead_code)]
fn t(
key: &str,
args: &std::collections::HashMap<String, FluentValue>
) -> String {
LOCALES.lookup_with_args(&LANGID, key, args)
}
#[allow(dead_code)]
fn e(
key: &str,
args: &std::collections::HashMap<String, FluentValue>
) -> crate::core::theme::PreEscaped<String> {
crate::core::theme::PreEscaped(
LOCALES.lookup_with_args(&LANGID, key, args)
)
}
};
}

View file

@ -1,8 +0,0 @@
#[actix_web::main]
async fn main() -> std::io::Result<()> {
// ```
// let app = pagetop::Application::build(None).await?;
// app.run()?.await
// ```
pagetop::Application::build(None).await?.run()?.await
}

View file

@ -1,21 +0,0 @@
//! Re-exporta recursos comunes.
pub use crate::{
args,
db_migrations,
};
pub use crate::config::SETTINGS;
pub use crate::trace;
pub use crate::localize;
pub use crate::db;
pub use crate::core::theme::*;
pub use crate::core::module::*;
pub use crate::core::response::page::*;
pub use crate::core::server;
pub use crate::base::component::*;
pub use crate::util;

View file

@ -1,2 +0,0 @@
pub use tracing::{Level, event, span};
pub use tracing::{debug, error, info, trace, warn};

View file

@ -1,53 +0,0 @@
#[macro_export]
/// Macro para construir grupos de pares clave-valor.
///
/// ```
/// let args = args![
/// "userName" => "Roberto",
/// "photoCount" => 3,
/// "userGender" => "male"
/// ];
/// ```
macro_rules! args {
( $($KEY:expr => $VALUE:expr),* ) => {{
let mut a = std::collections::HashMap::new();
$(
a.insert(String::from($KEY), $VALUE.into());
)*
a
}};
}
#[macro_export]
macro_rules! db_migrations {
( $DBCONN:ident ) => {{
$crate::run_now({
use $crate::db::migration::MigratorTrait;
migration::Migrator::up($DBCONN, None)
})
}};
}
pub fn valid_id(id: &str) -> Option<String> {
let id = id.trim().replace(" ", "_").to_lowercase();
match id.is_empty() {
true => None,
false => Some(id),
}
}
pub fn optional_str(s: &str) -> Option<String> {
let s = s.to_owned();
match s.is_empty() {
true => None,
false => Some(s),
}
}
pub fn assigned_str(optional: &Option<String>) -> &str {
match optional {
Some(o) => o.as_str(),
_ => "",
}
}