From 36c931486d0732d7b2b7c3b970124a5f6cc07b88 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sat, 16 Mar 2024 10:16:30 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20new=20components=20for=20page?= =?UTF-8?q?=20layout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/pagetop-admin/src/summary.rs | 1 - src/base/component.rs | 6 ++ src/base/component/layout.rs | 58 +++++++++++++++++++ src/base/component/region.rs | 37 ++++++++++++ src/core/component/context.rs | 14 +++++ src/core/theme.rs | 2 +- src/core/theme/definition.rs | 82 ++------------------------- src/response/page.rs | 18 +----- src/response/page/error.rs | 4 +- 9 files changed, 126 insertions(+), 96 deletions(-) create mode 100644 src/base/component/layout.rs create mode 100644 src/base/component/region.rs diff --git a/packages/pagetop-admin/src/summary.rs b/packages/pagetop-admin/src/summary.rs index ecdd1968..6abebc9a 100644 --- a/packages/pagetop-admin/src/summary.rs +++ b/packages/pagetop-admin/src/summary.rs @@ -152,7 +152,6 @@ pub async fn summary(request: HttpRequest) -> ResultPage { Page::new(request) .with_title(L10n::n("Admin")) - .with_template("admin") .with_component_in("top-menu", side_menu) .with_component( flex::Container::new() diff --git a/src/base/component.rs b/src/base/component.rs index a9912870..973a882c 100644 --- a/src/base/component.rs +++ b/src/base/component.rs @@ -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; diff --git a/src/base/component/layout.rs b/src/base/component/layout.rs new file mode 100644 index 00000000..dc531307 --- /dev/null +++ b/src/base/component/layout.rs @@ -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) + } +} diff --git a/src/base/component/region.rs b/src/base/component/region.rs new file mode 100644 index 00000000..41e3da27 --- /dev/null +++ b/src/base/component/region.rs @@ -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) -> Self { + Region::new().with_name(name) + } + + // Region BUILDER. + + #[fn_builder] + pub fn alter_name(&mut self, name: impl Into) -> &mut Self { + self.0.alter_value(name); + self + } + + // Region GETTERS. + + pub fn name(&self) -> &OptionId { + &self.0 + } +} diff --git a/src/core/component/context.rs b/src/core/component/context.rs index 1ca84e1b..02f96e46 100644 --- a/src/core/component/context.rs +++ b/src/core/component/context.rs @@ -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, // Stylesheets. headstyles: Assets, // Styles in head. javascript: Assets, // JavaScripts. @@ -50,6 +52,7 @@ impl Context { request, langid : &LANGID_DEFAULT, theme : *THEME_DEFAULT, + layout : "default", stylesheet: Assets::::new(), // Stylesheets. headstyles: Assets::::new(), // Styles in head. javascript: Assets::::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(&mut self, id: Option) -> String { diff --git a/src/core/theme.rs b/src/core/theme.rs index 594018a1..1925071a 100644 --- a/src/core/theme.rs +++ b/src/core/theme.rs @@ -1,5 +1,5 @@ mod definition; -pub use definition::{ThemeBuiltInClasses, ThemeRef, ThemeTrait}; +pub use definition::{ThemeRef, ThemeTrait}; mod regions; pub(crate) use regions::ComponentsInRegions; diff --git a/src/core/theme/definition.rs b/src/core/theme/definition.rs index ca111e3a..26b1ba68 100644 --- a/src/core/theme/definition.rs +++ b/src/core/theme/definition.rs @@ -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 { - 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())) } } } diff --git a/src/response/page.rs b/src/response/page.rs index 4ce4ddff..f7c9cb79 100644 --- a/src/response/page.rs +++ b/src/response/page.rs @@ -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 { diff --git a/src/response/page/error.rs b/src/response/page/error.rs index 4fad9540..8493d58f 100644 --- a/src/response/page/error.rs +++ b/src/response/page/error.rs @@ -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() {