Añade composición de páginas basada en componentes
This commit is contained in:
parent
0e3300dc90
commit
24e773c17b
30 changed files with 895 additions and 31 deletions
1
src/core/response/mod.rs
Normal file
1
src/core/response/mod.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
pub mod page;
|
||||
258
src/core/response/page/assets.rs
Normal file
258
src/core/response/page/assets.rs
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
use crate::core::theme::{Markup, PreEscaped, html};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// 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_string().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 Assets {
|
||||
favicon : Option<Favicon>,
|
||||
metadata : Vec<(String, String)>,
|
||||
stylesheets: Vec<StyleSheet>,
|
||||
javascripts: Vec<JavaScript>,
|
||||
seqid_count: u16,
|
||||
}
|
||||
|
||||
impl Assets {
|
||||
pub fn new() -> Self {
|
||||
Assets {
|
||||
favicon : None,
|
||||
metadata : Vec::new(),
|
||||
stylesheets: Vec::new(),
|
||||
javascripts: Vec::new(),
|
||||
seqid_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
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 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 GETTERS.
|
||||
|
||||
pub fn seqid(&mut self, id: &str) -> String {
|
||||
if id.is_empty() {
|
||||
self.seqid_count += 1;
|
||||
return format!("seqid-{}", self.seqid_count);
|
||||
}
|
||||
id.to_string()
|
||||
}
|
||||
}
|
||||
34
src/core/response/page/component.rs
Normal file
34
src/core/response/page/component.rs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
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 Component: Downcast + Send + Sync {
|
||||
|
||||
fn prepare() -> Self where Self: Sized;
|
||||
|
||||
fn qualified_name(&self) -> &str {
|
||||
type_name::<Self>()
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
""
|
||||
}
|
||||
|
||||
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!(Component);
|
||||
33
src/core/response/page/container.rs
Normal file
33
src/core/response/page/container.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
use crate::core::theme::{Markup, html};
|
||||
use crate::core::response::page::{PageAssets, PageComponent, render_component};
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Container(Vec<Arc<dyn PageComponent>>);
|
||||
|
||||
impl Container {
|
||||
pub fn new() -> Self {
|
||||
Container(Vec::new())
|
||||
}
|
||||
|
||||
pub fn new_with(component: impl PageComponent) -> Self {
|
||||
let mut container = Container::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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
12
src/core/response/page/mod.rs
Normal file
12
src/core/response/page/mod.rs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
pub mod assets;
|
||||
pub use assets::Assets as PageAssets;
|
||||
|
||||
mod component;
|
||||
pub use component::Component as PageComponent;
|
||||
|
||||
mod container;
|
||||
pub use container::Container as PageContainer;
|
||||
|
||||
mod page;
|
||||
pub use page::Page;
|
||||
pub use page::render_component;
|
||||
172
src/core/response/page/page.rs
Normal file
172
src/core/response/page/page.rs
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
use crate::config::SETTINGS;
|
||||
use crate::core::server;
|
||||
use crate::core::state::{COMPONENTS, THEME};
|
||||
use crate::core::theme::{DOCTYPE, Markup, html};
|
||||
use crate::core::response::page::{PageAssets, PageComponent, PageContainer};
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub enum TextDirection { LeftToRight, RightToLeft, Auto }
|
||||
|
||||
pub struct Page<'a> {
|
||||
language : &'a str,
|
||||
title : &'a str,
|
||||
direction : &'a str,
|
||||
description : &'a str,
|
||||
body_classes: Cow<'a, str>,
|
||||
assets : PageAssets,
|
||||
regions : HashMap<&'a str, PageContainer>,
|
||||
template : &'a str,
|
||||
}
|
||||
|
||||
impl<'a> Page<'a> {
|
||||
|
||||
pub fn prepare() -> Self {
|
||||
Page {
|
||||
language : &SETTINGS.app.language[..2],
|
||||
title : &SETTINGS.app.name,
|
||||
direction : "ltr",
|
||||
description : "",
|
||||
body_classes: "body".into(),
|
||||
assets : PageAssets::new(),
|
||||
regions : COMPONENTS.read().unwrap().clone(),
|
||||
template : "default",
|
||||
}
|
||||
}
|
||||
|
||||
// Page BUILDER.
|
||||
|
||||
pub fn with_language(&mut self, language: &'a str) -> &mut Self {
|
||||
self.language = language;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_title(&mut self, title: &'a str) -> &mut Self {
|
||||
self.title = title;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_direction(&mut self, dir: TextDirection) -> &mut Self {
|
||||
self.direction = match dir {
|
||||
TextDirection::LeftToRight => "ltr",
|
||||
TextDirection::RightToLeft => "rtl",
|
||||
_ => "auto"
|
||||
};
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_description(&mut self, description: &'a str) -> &mut Self {
|
||||
self.description = 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: &'a str) -> &mut Self {
|
||||
self.template = template;
|
||||
self
|
||||
}
|
||||
|
||||
// Page GETTERS.
|
||||
|
||||
pub fn language(&self) -> &str {
|
||||
self.language
|
||||
}
|
||||
|
||||
pub fn title(&self) -> &str {
|
||||
self.title
|
||||
}
|
||||
|
||||
pub fn direction(&self) -> TextDirection {
|
||||
match self.direction {
|
||||
"ltr" => TextDirection::LeftToRight,
|
||||
"rtl" => TextDirection::RightToLeft,
|
||||
_ => TextDirection::Auto
|
||||
}
|
||||
}
|
||||
|
||||
pub fn description(&self) -> &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
|
||||
}
|
||||
|
||||
// Page RENDER.
|
||||
|
||||
pub fn render(&mut self) -> server::Result<Markup> {
|
||||
// Acciones del tema antes de renderizar la página.
|
||||
THEME.before_render_page(self);
|
||||
|
||||
// Primero, renderizar el cuerpo.
|
||||
let body = THEME.render_page_body(self);
|
||||
|
||||
// Luego, renderizar la cabecera.
|
||||
let head = 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! {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_component(
|
||||
component: &dyn PageComponent,
|
||||
assets: &mut PageAssets
|
||||
) -> Markup {
|
||||
match component.is_renderable() {
|
||||
true => match THEME.render_component(component, assets) {
|
||||
Some(markup) => markup,
|
||||
None => component.default_render(assets)
|
||||
},
|
||||
false => html! {}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue