✨ Add new components for page layout
This commit is contained in:
parent
b6b7d9687b
commit
36c931486d
9 changed files with 126 additions and 96 deletions
|
|
@ -152,7 +152,6 @@ pub async fn summary(request: HttpRequest) -> ResultPage<Markup, ErrorPage> {
|
|||
|
||||
Page::new(request)
|
||||
.with_title(L10n::n("Admin"))
|
||||
.with_template("admin")
|
||||
.with_component_in("top-menu", side_menu)
|
||||
.with_component(
|
||||
flex::Container::new()
|
||||
|
|
|
|||
|
|
@ -157,6 +157,12 @@ impl ToString for FontSize {
|
|||
|
||||
// *************************************************************************************************
|
||||
|
||||
mod layout;
|
||||
pub use layout::Layout;
|
||||
|
||||
mod region;
|
||||
pub use region::Region;
|
||||
|
||||
mod html;
|
||||
pub use html::Html;
|
||||
|
||||
|
|
|
|||
58
src/base/component/layout.rs
Normal file
58
src/base/component/layout.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
#[derive(AutoDefault)]
|
||||
pub struct Layout;
|
||||
|
||||
impl ComponentTrait for Layout {
|
||||
fn new() -> Self {
|
||||
Layout
|
||||
}
|
||||
|
||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
||||
PrepareMarkup::With(match cx.layout() {
|
||||
"default" => Self::default_layout(cx),
|
||||
"admin" => Self::admin_layout(cx),
|
||||
_ => Self::default_layout(cx),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Layout {
|
||||
fn default_layout(cx: &mut Context) -> Markup {
|
||||
flex::Container::new()
|
||||
.with_id("body__wrapper")
|
||||
.with_direction(flex::Direction::Column(BreakPoint::None))
|
||||
.with_items_align(flex::ItemAlign::Center)
|
||||
.add_item(flex::Item::full(Region::named("header")).with_id("header"))
|
||||
.add_item(flex::Item::full(Region::named("pagetop")).with_id("pagetop"))
|
||||
.add_item(flex::Item::full(
|
||||
flex::Container::new()
|
||||
.with_id("content__wrapper")
|
||||
.with_direction(flex::Direction::Row(BreakPoint::None))
|
||||
.add_item(
|
||||
flex::Item::with(Region::named("sidebar_left"))
|
||||
.with_id("sidebar_left")
|
||||
.with_grow(flex::ItemGrow::Is1),
|
||||
)
|
||||
.add_item(
|
||||
flex::Item::with(Region::named("content"))
|
||||
.with_id("content")
|
||||
.with_grow(flex::ItemGrow::Is3),
|
||||
)
|
||||
.add_item(
|
||||
flex::Item::with(Region::named("sidebar_right"))
|
||||
.with_id("sidebar_right")
|
||||
.with_grow(flex::ItemGrow::Is1),
|
||||
),
|
||||
))
|
||||
.add_item(flex::Item::full(Region::named("footer")).with_id("footer"))
|
||||
.render(cx)
|
||||
}
|
||||
|
||||
fn admin_layout(cx: &mut Context) -> Markup {
|
||||
Html::with(html! {
|
||||
("admin")
|
||||
})
|
||||
.render(cx)
|
||||
}
|
||||
}
|
||||
37
src/base/component/region.rs
Normal file
37
src/base/component/region.rs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
#[derive(AutoDefault)]
|
||||
pub struct Region(OptionId);
|
||||
|
||||
impl ComponentTrait for Region {
|
||||
fn new() -> Self {
|
||||
Region::default()
|
||||
}
|
||||
|
||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
||||
if let Some(name) = self.name().get() {
|
||||
return PrepareMarkup::With(cx.prepare_region(name.as_str()));
|
||||
}
|
||||
PrepareMarkup::None
|
||||
}
|
||||
}
|
||||
|
||||
impl Region {
|
||||
pub fn named(name: impl Into<String>) -> Self {
|
||||
Region::new().with_name(name)
|
||||
}
|
||||
|
||||
// Region BUILDER.
|
||||
|
||||
#[fn_builder]
|
||||
pub fn alter_name(&mut self, name: impl Into<String>) -> &mut Self {
|
||||
self.0.alter_value(name);
|
||||
self
|
||||
}
|
||||
|
||||
// Region GETTERS.
|
||||
|
||||
pub fn name(&self) -> &OptionId {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ use std::str::FromStr;
|
|||
pub enum AssetsOp {
|
||||
LangId(&'static LanguageIdentifier),
|
||||
Theme(&'static str),
|
||||
Layout(&'static str),
|
||||
// Stylesheets.
|
||||
AddStyleSheet(StyleSheet),
|
||||
RemoveStyleSheet(&'static str),
|
||||
|
|
@ -34,6 +35,7 @@ pub struct Context {
|
|||
request : HttpRequest,
|
||||
langid : &'static LanguageIdentifier,
|
||||
theme : ThemeRef,
|
||||
layout : &'static str,
|
||||
stylesheet: Assets<StyleSheet>, // Stylesheets.
|
||||
headstyles: Assets<HeadStyles>, // Styles in head.
|
||||
javascript: Assets<JavaScript>, // JavaScripts.
|
||||
|
|
@ -50,6 +52,7 @@ impl Context {
|
|||
request,
|
||||
langid : &LANGID_DEFAULT,
|
||||
theme : *THEME_DEFAULT,
|
||||
layout : "default",
|
||||
stylesheet: Assets::<StyleSheet>::new(), // Stylesheets.
|
||||
headstyles: Assets::<HeadStyles>::new(), // Styles in head.
|
||||
javascript: Assets::<JavaScript>::new(), // JavaScripts.
|
||||
|
|
@ -69,6 +72,9 @@ impl Context {
|
|||
AssetsOp::Theme(theme_name) => {
|
||||
self.theme = theme_by_single_name(theme_name).unwrap_or(*THEME_DEFAULT);
|
||||
}
|
||||
AssetsOp::Layout(layout) => {
|
||||
self.layout = layout;
|
||||
}
|
||||
|
||||
// Stylesheets.
|
||||
AssetsOp::AddStyleSheet(css) => { self.stylesheet.add(css); }
|
||||
|
|
@ -118,6 +124,10 @@ impl Context {
|
|||
self.theme
|
||||
}
|
||||
|
||||
pub fn layout(&self) -> &str {
|
||||
self.layout
|
||||
}
|
||||
|
||||
pub fn regions(&self) -> &ComponentsInRegions {
|
||||
&self.regions
|
||||
}
|
||||
|
|
@ -142,6 +152,10 @@ impl Context {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn prepare_region(&mut self, region: &str) -> Markup {
|
||||
self.regions.all_components(self.theme, region).render(self)
|
||||
}
|
||||
|
||||
// Context EXTRAS.
|
||||
|
||||
pub fn required_id<T>(&mut self, id: Option<String>) -> String {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
mod definition;
|
||||
pub use definition::{ThemeBuiltInClasses, ThemeRef, ThemeTrait};
|
||||
pub use definition::{ThemeRef, ThemeTrait};
|
||||
|
||||
mod regions;
|
||||
pub(crate) use regions::ComponentsInRegions;
|
||||
|
|
|
|||
|
|
@ -1,48 +1,13 @@
|
|||
use crate::core::component::{ComponentTrait, Context};
|
||||
use crate::base::component::Layout;
|
||||
use crate::core::component::{ComponentBase, ComponentTrait, Context};
|
||||
use crate::core::package::PackageTrait;
|
||||
use crate::html::{html, Favicon, Markup, OptionId};
|
||||
use crate::html::{html, Favicon, Markup};
|
||||
use crate::locale::L10n;
|
||||
use crate::response::page::Page;
|
||||
use crate::{concat_string, config};
|
||||
|
||||
pub type ThemeRef = &'static dyn ThemeTrait;
|
||||
|
||||
/// Theme built-in classes used by the default page rendering process.
|
||||
///
|
||||
/// The [`ThemeTrait`](crate::core::theme::ThemeTrait) default implementation uses these CSS classes
|
||||
/// in the [`prepare_region()`](crate::core::theme::ThemeTrait::prepare_region) and
|
||||
/// [`prepare_body()`](crate::core::theme::ThemeTrait::prepare_body) methods to build the HTML code
|
||||
/// for regions and page body main containers.
|
||||
///
|
||||
/// Theme developers can customize the default implementation of
|
||||
/// [`builtin_classes()`](crate::core::theme::ThemeTrait::builtin_classes) method to return
|
||||
/// alternative class name or space-separated class names for each variant, without altering the
|
||||
/// default page rendering process.
|
||||
pub enum ThemeBuiltInClasses {
|
||||
/// Skip to content link. Default is `skip__to_content`.
|
||||
SkipToContent,
|
||||
/// Main body wrapper. Default is `body__wrapper`.
|
||||
BodyWrapper,
|
||||
/// Main content wrapper. Default is `content__wrapper`.
|
||||
ContentWrapper,
|
||||
/// A region container. Default is `region__container`.
|
||||
RegionContainer,
|
||||
/// The region inner content. Default is `region__content`.
|
||||
RegionContent,
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
impl ToString for ThemeBuiltInClasses {
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
ThemeBuiltInClasses::SkipToContent => String::from("skip__to_content"),
|
||||
ThemeBuiltInClasses::BodyWrapper => String::from("body__wrapper"),
|
||||
ThemeBuiltInClasses::ContentWrapper => String::from("content__wrapper"),
|
||||
ThemeBuiltInClasses::RegionContainer => String::from("region__container"),
|
||||
ThemeBuiltInClasses::RegionContent => String::from("region__content"),
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Los temas deben implementar este "trait".
|
||||
pub trait ThemeTrait: PackageTrait + Send + Sync {
|
||||
#[rustfmt::skip]
|
||||
|
|
@ -57,34 +22,6 @@ pub trait ThemeTrait: PackageTrait + Send + Sync {
|
|||
]
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
/// Return the name of the CSS class or space-separated class names associated with each variant
|
||||
/// of [ThemeBuiltInClasses].
|
||||
///
|
||||
/// Theme developers can customize the default implementation of this method to return
|
||||
/// alternative class name or space-separated class names for each variant, without altering the
|
||||
/// default page rendering process.
|
||||
fn builtin_classes(&self, builtin: ThemeBuiltInClasses) -> Option<String> {
|
||||
Some(builtin.to_string())
|
||||
}
|
||||
|
||||
fn prepare_region(&self, page: &mut Page, region_name: &str) -> Markup {
|
||||
let render_region = page.components_in(region_name).render(page.context());
|
||||
if render_region.is_empty() {
|
||||
return html! {};
|
||||
}
|
||||
html! {
|
||||
div
|
||||
id=[OptionId::new(region_name).get()]
|
||||
class=[self.builtin_classes(ThemeBuiltInClasses::RegionContainer)]
|
||||
{
|
||||
div class=[self.builtin_classes(ThemeBuiltInClasses::RegionContent)] {
|
||||
(render_region)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn before_prepare_body(&self, page: &mut Page) {}
|
||||
|
||||
|
|
@ -94,20 +31,11 @@ pub trait ThemeTrait: PackageTrait + Send + Sync {
|
|||
html! {
|
||||
body id=[page.body_id().get()] class=[page.body_classes().get()] {
|
||||
@if let Some(skip) = L10n::l("skip_to_content").using(page.context().langid()) {
|
||||
div class=[self.builtin_classes(ThemeBuiltInClasses::SkipToContent)] {
|
||||
div class="skip__to_content" {
|
||||
a href=(skip_to) { (skip) }
|
||||
}
|
||||
}
|
||||
div class=[self.builtin_classes(ThemeBuiltInClasses::BodyWrapper)] {
|
||||
(self.prepare_region(page, "header"))
|
||||
(self.prepare_region(page, "pagetop"))
|
||||
div class=[self.builtin_classes(ThemeBuiltInClasses::ContentWrapper)] {
|
||||
(self.prepare_region(page, "sidebar_left"))
|
||||
(self.prepare_region(page, "content"))
|
||||
(self.prepare_region(page, "sidebar_right"))
|
||||
}
|
||||
(self.prepare_region(page, "footer"))
|
||||
}
|
||||
(Layout::new().render(page.context()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ pub use error::ErrorPage;
|
|||
pub use actix_web::Result as ResultPage;
|
||||
|
||||
use crate::base::action;
|
||||
use crate::core::component::{AnyComponent, ComponentTrait, MixedComponents, MixedOp};
|
||||
use crate::core::component::{AnyComponent, ComponentTrait, MixedOp};
|
||||
use crate::core::component::{AssetsOp, Context};
|
||||
use crate::fn_builder;
|
||||
use crate::html::{html, Markup, DOCTYPE};
|
||||
|
|
@ -25,7 +25,6 @@ pub struct Page {
|
|||
body_id : OptionId,
|
||||
body_classes: OptionClasses,
|
||||
skip_to : OptionId,
|
||||
template : String,
|
||||
}
|
||||
|
||||
impl Page {
|
||||
|
|
@ -41,7 +40,6 @@ impl Page {
|
|||
body_id : OptionId::default(),
|
||||
body_classes: OptionClasses::default(),
|
||||
skip_to : OptionId::default(),
|
||||
template : "default".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -102,8 +100,8 @@ impl Page {
|
|||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn alter_template(&mut self, template: &str) -> &mut Self {
|
||||
self.template = template.to_owned();
|
||||
pub fn alter_layout(&mut self, layout: &'static str) -> &mut Self {
|
||||
self.context.alter_assets(AssetsOp::Layout(layout));
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -167,16 +165,6 @@ impl Page {
|
|||
&self.skip_to
|
||||
}
|
||||
|
||||
pub fn template(&self) -> &str {
|
||||
self.template.as_str()
|
||||
}
|
||||
|
||||
pub fn components_in(&self, region: &str) -> MixedComponents {
|
||||
self.context
|
||||
.regions()
|
||||
.all_components(self.context.theme(), region)
|
||||
}
|
||||
|
||||
// Page RENDER.
|
||||
|
||||
pub fn render(&mut self) -> ResultPage<Markup, ErrorPage> {
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ impl fmt::Display for ErrorPage {
|
|||
let error_page = Page::new(request.clone());
|
||||
if let Ok(page) = error_page
|
||||
.with_title(L10n::n("Error FORBIDDEN"))
|
||||
.with_template("error")
|
||||
.with_layout("error")
|
||||
.with_component(Error403)
|
||||
.render()
|
||||
{
|
||||
|
|
@ -45,7 +45,7 @@ impl fmt::Display for ErrorPage {
|
|||
let error_page = Page::new(request.clone());
|
||||
if let Ok(page) = error_page
|
||||
.with_title(L10n::n("Error RESOURCE NOT FOUND"))
|
||||
.with_template("error")
|
||||
.with_layout("error")
|
||||
.with_component(Error404)
|
||||
.render()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue