diff --git a/src/base/component.rs b/src/base/component.rs index 4edfc9a..4df64ff 100644 --- a/src/base/component.rs +++ b/src/base/component.rs @@ -1,53 +1,5 @@ //! Componentes nativos proporcionados por PageTop. -use crate::AutoDefault; - -use std::fmt; - -// **< FontSize >*********************************************************************************** - -#[derive(AutoDefault)] -pub enum FontSize { - ExtraLarge, - XxLarge, - XLarge, - Large, - Medium, - #[default] - Normal, - Small, - XSmall, - XxSmall, - ExtraSmall, -} - -#[rustfmt::skip] -impl FontSize { - #[inline] - pub const fn as_str(&self) -> &'static str { - match self { - FontSize::ExtraLarge => "fs__x3l", - FontSize::XxLarge => "fs__x2l", - FontSize::XLarge => "fs__xl", - FontSize::Large => "fs__l", - FontSize::Medium => "fs__m", - FontSize::Normal => "", - FontSize::Small => "fs__s", - FontSize::XSmall => "fs__xs", - FontSize::XxSmall => "fs__x2s", - FontSize::ExtraSmall => "fs__x3s", - } - } -} - -impl fmt::Display for FontSize { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(self.as_str()) - } -} - -// ************************************************************************************************* - mod html; pub use html::Html; @@ -56,8 +8,3 @@ pub use block::Block; mod poweredby; pub use poweredby::PoweredBy; - -mod icon; -pub use icon::{Icon, IconKind}; - -pub mod menu; diff --git a/src/base/component/block.rs b/src/base/component/block.rs index 9a04c4e..c96f2ba 100644 --- a/src/base/component/block.rs +++ b/src/base/component/block.rs @@ -47,7 +47,7 @@ impl Component for Block { } impl Block { - // **< Block BUILDER >************************************************************************** + // Block BUILDER ******************************************************************************* /// Establece el identificador único (`id`) del bloque. #[builder_fn] @@ -77,14 +77,14 @@ impl Block { self } - /// Modifica la lista de hijos (`children`) aplicando una operación [`ChildOp`]. + /// Modifica la lista de hijos (`children`) aplicando una operación. #[builder_fn] pub fn with_child(mut self, op: ChildOp) -> Self { self.children.alter_child(op); self } - // **< Block GETTERS >************************************************************************** + // Block GETTERS ******************************************************************************* /// Devuelve las clases CSS asociadas al bloque. pub fn classes(&self) -> &AttrClasses { diff --git a/src/base/component/html.rs b/src/base/component/html.rs index b8c4aaa..8fa5690 100644 --- a/src/base/component/html.rs +++ b/src/base/component/html.rs @@ -49,7 +49,7 @@ impl Component for Html { } impl Html { - // **< Html BUILDER >*************************************************************************** + // Html BUILDER ******************************************************************************** /// Crea una instancia que generará el `Markup`, con acceso opcional al contexto. /// @@ -77,7 +77,7 @@ impl Html { self } - // **< Html GETTERS >*************************************************************************** + // Html GETTERS ******************************************************************************** /// Aplica la función interna de renderizado con el [`Context`] proporcionado. /// diff --git a/src/base/component/icon.rs b/src/base/component/icon.rs deleted file mode 100644 index 73e5ac4..0000000 --- a/src/base/component/icon.rs +++ /dev/null @@ -1,134 +0,0 @@ -use crate::prelude::*; - -const DEFAULT_VIEWBOX: &str = "0 0 16 16"; - -#[derive(AutoDefault)] -pub enum IconKind { - #[default] - None, - Font(FontSize), - Svg { - shapes: Markup, - viewbox: AttrValue, - }, -} - -#[rustfmt::skip] -#[derive(AutoDefault)] -pub struct Icon { - classes : AttrClasses, - icon_kind : IconKind, - aria_label: AttrL10n, -} - -impl Component for Icon { - fn new() -> Self { - Icon::default() - } - - fn setup_before_prepare(&mut self, _cx: &mut Context) { - if !matches!(self.icon_kind(), IconKind::None) { - self.alter_classes(ClassesOp::Prepend, "icon"); - } - if let IconKind::Font(font_size) = self.icon_kind() { - self.alter_classes(ClassesOp::Add, font_size.as_str()); - } - } - - fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { - match self.icon_kind() { - IconKind::None => PrepareMarkup::None, - IconKind::Font(_) => { - let aria_label = self.aria_label().lookup(cx); - let has_label = aria_label.is_some(); - PrepareMarkup::With(html! { - i - class=[self.classes().get()] - role=[has_label.then_some("img")] - aria-label=[aria_label] - aria-hidden=[(!has_label).then_some("true")] - {} - }) - } - IconKind::Svg { shapes, viewbox } => { - let aria_label = self.aria_label().lookup(cx); - let has_label = aria_label.is_some(); - let viewbox = viewbox.get().unwrap_or_else(|| DEFAULT_VIEWBOX.to_string()); - PrepareMarkup::With(html! { - svg - xmlns="http://www.w3.org/2000/svg" - viewBox=(viewbox) - fill="currentColor" - focusable="false" - class=[self.classes().get()] - role=[has_label.then_some("img")] - aria-label=[aria_label] - aria-hidden=[(!has_label).then_some("true")] - { - (shapes) - } - }) - } - } - } -} - -impl Icon { - pub fn font() -> Self { - Icon::default().with_icon_kind(IconKind::Font(FontSize::default())) - } - - pub fn font_sized(font_size: FontSize) -> Self { - Icon::default().with_icon_kind(IconKind::Font(font_size)) - } - - pub fn svg(shapes: Markup) -> Self { - Icon::default().with_icon_kind(IconKind::Svg { - shapes, - viewbox: AttrValue::default(), - }) - } - - pub fn svg_with_viewbox(shapes: Markup, viewbox: impl AsRef) -> Self { - Icon::default().with_icon_kind(IconKind::Svg { - shapes, - viewbox: AttrValue::new(viewbox), - }) - } - - // **< Icon BUILDER >*************************************************************************** - - /// Modifica la lista de clases CSS aplicadas al icono. - #[builder_fn] - pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef) -> Self { - self.classes.alter_value(op, classes); - self - } - - #[builder_fn] - pub fn with_icon_kind(mut self, icon_kind: IconKind) -> Self { - self.icon_kind = icon_kind; - self - } - - #[builder_fn] - pub fn with_aria_label(mut self, label: L10n) -> Self { - self.aria_label.alter_value(label); - self - } - - // **< Icon GETTERS >*************************************************************************** - - /// Devuelve las clases CSS asociadas al icono. - pub fn classes(&self) -> &AttrClasses { - &self.classes - } - - pub fn icon_kind(&self) -> &IconKind { - &self.icon_kind - } - - pub fn aria_label(&self) -> &AttrL10n { - &self.aria_label - } -} diff --git a/src/base/component/menu.rs b/src/base/component/menu.rs deleted file mode 100644 index 14f7589..0000000 --- a/src/base/component/menu.rs +++ /dev/null @@ -1,17 +0,0 @@ -mod menu_menu; -pub use menu_menu::Menu; - -mod item; -pub use item::{Item, ItemKind}; - -mod submenu; -pub use submenu::Submenu; - -mod megamenu; -pub use megamenu::Megamenu; - -mod group; -pub use group::Group; - -mod element; -pub use element::{Element, ElementType}; diff --git a/src/base/component/menu/element.rs b/src/base/component/menu/element.rs deleted file mode 100644 index 6d14204..0000000 --- a/src/base/component/menu/element.rs +++ /dev/null @@ -1,56 +0,0 @@ -use crate::prelude::*; - -type Content = Typed; -type SubmenuItems = Typed; - -#[derive(AutoDefault)] -pub enum ElementType { - #[default] - Void, - Html(Content), - Submenu(SubmenuItems), -} - -#[rustfmt::skip] -#[derive(AutoDefault)] -pub struct Element { - element_type: ElementType, -} - -impl Component for Element { - fn new() -> Self { - Element::default() - } - - fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { - match self.element_type() { - ElementType::Void => PrepareMarkup::None, - ElementType::Html(content) => PrepareMarkup::With(html! { - (content.render(cx)) - }), - ElementType::Submenu(submenu) => PrepareMarkup::With(html! { - (submenu.render(cx)) - }), - } - } -} - -impl Element { - pub fn html(content: Html) -> Self { - Element { - element_type: ElementType::Html(Content::with(content)), - } - } - - pub fn submenu(submenu: menu::Submenu) -> Self { - Element { - element_type: ElementType::Submenu(SubmenuItems::with(submenu)), - } - } - - // **< Element GETTERS >************************************************************************ - - pub fn element_type(&self) -> &ElementType { - &self.element_type - } -} diff --git a/src/base/component/menu/group.rs b/src/base/component/menu/group.rs deleted file mode 100644 index ba18893..0000000 --- a/src/base/component/menu/group.rs +++ /dev/null @@ -1,58 +0,0 @@ -use crate::prelude::*; - -#[rustfmt::skip] -#[derive(AutoDefault)] -pub struct Group { - id : AttrId, - elements: Children, -} - -impl Component for Group { - fn new() -> Self { - Group::default() - } - - fn id(&self) -> Option { - self.id.get() - } - - fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { - PrepareMarkup::With(html! { - div id=[self.id()] class="menu__group" { - (self.elements().render(cx)) - } - }) - } -} - -impl Group { - // **< Group BUILDER >************************************************************************** - - /// Establece el identificador único (`id`) del grupo. - #[builder_fn] - pub fn with_id(mut self, id: impl AsRef) -> Self { - self.id.alter_value(id); - self - } - - /// Añade un nuevo elemento al menú. - pub fn add_element(mut self, element: menu::Element) -> Self { - self.elements - .alter_typed(TypedOp::Add(Typed::with(element))); - self - } - - /// Modifica la lista de elementos (`children`) aplicando una operación [`TypedOp`]. - #[builder_fn] - pub fn with_elements(mut self, op: TypedOp) -> Self { - self.elements.alter_typed(op); - self - } - - // **< Group GETTERS >************************************************************************** - - /// Devuelve la lista de elementos (`children`) del grupo. - pub fn elements(&self) -> &Children { - &self.elements - } -} diff --git a/src/base/component/menu/item.rs b/src/base/component/menu/item.rs deleted file mode 100644 index 7a36fbb..0000000 --- a/src/base/component/menu/item.rs +++ /dev/null @@ -1,185 +0,0 @@ -use crate::prelude::*; - -type Label = L10n; -type Content = Typed; -type SubmenuItems = Typed; -type MegamenuGroups = Typed; - -#[derive(AutoDefault)] -pub enum ItemKind { - #[default] - Void, - Label(Label), - Link(Label, FnPathByContext), - LinkBlank(Label, FnPathByContext), - Html(Content), - Submenu(Label, SubmenuItems), - Megamenu(Label, MegamenuGroups), -} - -#[rustfmt::skip] -#[derive(AutoDefault)] -pub struct Item { - item_kind : ItemKind, - description: AttrL10n, - left_icon : Typed, - right_icon : Typed, -} - -impl Component for Item { - fn new() -> Self { - Item::default() - } - - fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { - let description = self.description().lookup(cx); - let left_icon = self.left_icon().render(cx); - let right_icon = self.right_icon().render(cx); - - match self.item_kind() { - ItemKind::Void => PrepareMarkup::None, - ItemKind::Label(label) => PrepareMarkup::With(html! { - li class="menu__item menu__item--label" { - span title=[description] { - (left_icon) - span class="menu__label" { (label.using(cx)) } - (right_icon) - } - } - }), - ItemKind::Link(label, path) => PrepareMarkup::With(html! { - li class="menu__item menu__item--link" { - a href=(path(cx)) title=[description] { - (left_icon) - span class="menu__label" { (label.using(cx)) } - (right_icon) - } - } - }), - ItemKind::LinkBlank(label, path) => PrepareMarkup::With(html! { - li class="menu__item menu__item--link" { - a href=(path(cx)) title=[description] target="_blank" { - (left_icon) - span class="menu__label" { (label.using(cx)) } - (right_icon) - } - } - }), - ItemKind::Html(content) => PrepareMarkup::With(html! { - li class="menu__item menu__item--html" { - (content.render(cx)) - } - }), - ItemKind::Submenu(label, submenu) => PrepareMarkup::With(html! { - li class="menu__item menu__item--children" { - a href="#" title=[description] { - (left_icon) - span class="menu__label" { (label.using(cx)) } - (Icon::svg(html! { - path fill-rule="evenodd" d="M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708" {} - }).render(cx)) - } - div class="menu__children menu__children--submenu" { - (submenu.render(cx)) - } - } - }), - ItemKind::Megamenu(label, megamenu) => PrepareMarkup::With(html! { - li class="menu__item menu__item--children" { - a href="#" title=[description] { - (left_icon) - span class="menu__label" { (label.using(cx)) } - (Icon::svg(html! { - path fill-rule="evenodd" d="M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708" {} - }).render(cx)) - } - div class="menu__children menu__children--mega" { - (megamenu.render(cx)) - } - } - }), - } - } -} - -impl Item { - pub fn label(label: L10n) -> Self { - Item { - item_kind: ItemKind::Label(label), - ..Default::default() - } - } - - pub fn link(label: L10n, path: FnPathByContext) -> Self { - Item { - item_kind: ItemKind::Link(label, path), - ..Default::default() - } - } - - pub fn link_blank(label: L10n, path: FnPathByContext) -> Self { - Item { - item_kind: ItemKind::LinkBlank(label, path), - ..Default::default() - } - } - - pub fn html(content: Html) -> Self { - Item { - item_kind: ItemKind::Html(Content::with(content)), - ..Default::default() - } - } - - pub fn submenu(label: L10n, submenu: menu::Submenu) -> Self { - Item { - item_kind: ItemKind::Submenu(label, SubmenuItems::with(submenu)), - ..Default::default() - } - } - /* - pub fn megamenu(label: L10n, megamenu: Megamenu) -> Self { - Item { - item_kind: ItemKind::Megamenu(label, MegamenuGroups::with(megamenu)), - ..Default::default() - } - } - */ - // **< Item BUILDER >*************************************************************************** - - #[builder_fn] - pub fn with_description(mut self, text: L10n) -> Self { - self.description.alter_value(text); - self - } - - #[builder_fn] - pub fn with_left_icon>(mut self, icon: Option) -> Self { - self.left_icon.alter_component(icon.map(Into::into)); - self - } - - #[builder_fn] - pub fn with_right_icon>(mut self, icon: Option) -> Self { - self.right_icon.alter_component(icon.map(Into::into)); - self - } - - // **< Item GETTERS >*************************************************************************** - - pub fn item_kind(&self) -> &ItemKind { - &self.item_kind - } - - pub fn description(&self) -> &AttrL10n { - &self.description - } - - pub fn left_icon(&self) -> &Typed { - &self.left_icon - } - - pub fn right_icon(&self) -> &Typed { - &self.right_icon - } -} diff --git a/src/base/component/menu/megamenu.rs b/src/base/component/menu/megamenu.rs deleted file mode 100644 index e435e75..0000000 --- a/src/base/component/menu/megamenu.rs +++ /dev/null @@ -1,57 +0,0 @@ -use crate::prelude::*; - -#[rustfmt::skip] -#[derive(AutoDefault)] -pub struct Megamenu { - id : AttrId, - groups: Children, -} - -impl Component for Megamenu { - fn new() -> Self { - Megamenu::default() - } - - fn id(&self) -> Option { - self.id.get() - } - - fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { - PrepareMarkup::With(html! { - div id=[self.id()] class="menu__mega" { - (self.groups().render(cx)) - } - }) - } -} - -impl Megamenu { - // **< Megamenu BUILDER >*********************************************************************** - - /// Establece el identificador único (`id`) del megamenú. - #[builder_fn] - pub fn with_id(mut self, id: impl AsRef) -> Self { - self.id.alter_value(id); - self - } - - /// Añade un nuevo grupo al menú. - pub fn add_group(mut self, group: menu::Group) -> Self { - self.groups.alter_typed(TypedOp::Add(Typed::with(group))); - self - } - - /// Modifica la lista de grupos (`children`) aplicando una operación [`TypedOp`]. - #[builder_fn] - pub fn with_groups(mut self, op: TypedOp) -> Self { - self.groups.alter_typed(op); - self - } - - // **< Megamenu GETTERS >*********************************************************************** - - /// Devuelve la lista de grupos (`children`) del megamenú. - pub fn groups(&self) -> &Children { - &self.groups - } -} diff --git a/src/base/component/menu/menu_menu.rs b/src/base/component/menu/menu_menu.rs deleted file mode 100644 index 58a4c21..0000000 --- a/src/base/component/menu/menu_menu.rs +++ /dev/null @@ -1,108 +0,0 @@ -use crate::prelude::*; - -#[rustfmt::skip] -#[derive(AutoDefault)] -pub struct Menu { - id : AttrId, - classes: AttrClasses, - items : Children, -} - -impl Component for Menu { - fn new() -> Self { - Menu::default() - } - - fn id(&self) -> Option { - self.id.get() - } - - fn setup_before_prepare(&mut self, _cx: &mut Context) { - self.alter_classes(ClassesOp::Prepend, "menu"); - } - - fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { - // cx.set_param::(PARAM_BASE_INCLUDE_MENU_ASSETS, &true); - // cx.set_param::(PARAM_BASE_INCLUDE_ICONS, &true); - - PrepareMarkup::With(html! { - div id=[self.id()] class=[self.classes().get()] { - div class="menu__wrapper" { - div class="menu__panel" { - div class="menu__overlay" {} - nav class="menu__nav" { - div class="menu__header" { - button type="button" class="menu__back" { - (Icon::svg(html! { - path fill-rule="evenodd" d="M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0" {} - }).render(cx)) - } - div class="menu__title" {} - button type="button" class="menu__close" { - (Icon::svg(html! { - path d="M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8z" {} - }).render(cx)) - } - } - ul class="menu__list" { - (self.items().render(cx)) - } - } - } - button - type="button" - class="menu__trigger" - title=[L10n::l("menu_toggle").lookup(cx)] - { - (Icon::svg(html! { - path fill-rule="evenodd" d="M2.5 12a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5" {} - }).render(cx)) - } - } - } - }) - } -} - -impl Menu { - // **< Menu BUILDER >*************************************************************************** - - /// Establece el identificador único (`id`) del menú. - #[builder_fn] - pub fn with_id(mut self, id: impl AsRef) -> Self { - self.id.alter_value(id); - self - } - - /// Modifica la lista de clases CSS aplicadas al menú. - #[builder_fn] - pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef) -> Self { - self.classes.alter_value(op, classes); - self - } - - /// Añade un nuevo ítem al menú. - pub fn add_item(mut self, item: menu::Item) -> Self { - self.items.alter_typed(TypedOp::Add(Typed::with(item))); - self - } - - /// Modifica la lista de ítems (`children`) aplicando una operación [`TypedOp`]. - #[builder_fn] - pub fn with_items(mut self, op: TypedOp) -> Self { - self.items.alter_typed(op); - self - } - - // **< Menu GETTERS >*************************************************************************** - - /// Devuelve las clases CSS asociadas al menú. - pub fn classes(&self) -> &AttrClasses { - &self.classes - } - - /// Devuelve la lista de ítems (`children`) del menú. - pub fn items(&self) -> &Children { - &self.items - } -} diff --git a/src/base/component/menu/submenu.rs b/src/base/component/menu/submenu.rs deleted file mode 100644 index a5957ef..0000000 --- a/src/base/component/menu/submenu.rs +++ /dev/null @@ -1,73 +0,0 @@ -use crate::prelude::*; - -#[rustfmt::skip] -#[derive(AutoDefault)] -pub struct Submenu { - id : AttrId, - title: AttrL10n, - items: Children, -} - -impl Component for Submenu { - fn new() -> Self { - Submenu::default() - } - - fn id(&self) -> Option { - self.id.get() - } - - fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { - PrepareMarkup::With(html! { - div id=[self.id()] class="menu__submenu" { - @if let Some(title) = self.title().lookup(cx) { - h4 class="menu__submenu-title" { (title) } - } - ul { - (self.items().render(cx)) - } - } - }) - } -} - -impl Submenu { - // **< Submenu BUILDER >************************************************************************ - - /// Establece el identificador único (`id`) del submenú. - #[builder_fn] - pub fn with_id(mut self, id: impl AsRef) -> Self { - self.id.alter_value(id); - self - } - - #[builder_fn] - pub fn with_title(mut self, title: L10n) -> Self { - self.title.alter_value(title); - self - } - - /// Añade un nuevo ítem al submenú. - pub fn add_item(mut self, item: menu::Item) -> Self { - self.items.alter_typed(TypedOp::Add(Typed::with(item))); - self - } - - /// Modifica la lista de ítems (`children`) aplicando una operación [`TypedOp`]. - #[builder_fn] - pub fn with_items(mut self, op: TypedOp) -> Self { - self.items.alter_typed(op); - self - } - - // **< Submenu GETTERS >************************************************************************ - - pub fn title(&self) -> &AttrL10n { - &self.title - } - - /// Devuelve la lista de ítems (`children`) del submenú. - pub fn items(&self) -> &Children { - &self.items - } -} diff --git a/src/base/component/poweredby.rs b/src/base/component/poweredby.rs index 4b54af3..bfe3835 100644 --- a/src/base/component/poweredby.rs +++ b/src/base/component/poweredby.rs @@ -39,7 +39,7 @@ impl Component for PoweredBy { } impl PoweredBy { - // **< PoweredBy BUILDER >********************************************************************** + // PoweredBy BUILDER *************************************************************************** /// Establece el texto de copyright que mostrará el componente. /// @@ -58,7 +58,7 @@ impl PoweredBy { self } - // **< PoweredBy GETTERS >********************************************************************** + // PoweredBy GETTERS *************************************************************************** /// Devuelve el texto de copyright actual, si existe. pub fn copyright(&self) -> Option<&str> { diff --git a/src/base/theme/basic.rs b/src/base/theme/basic.rs index ecd6485..2f49274 100644 --- a/src/base/theme/basic.rs +++ b/src/base/theme/basic.rs @@ -51,35 +51,14 @@ impl Theme for Basic { "PageTopIntro" => "/css/intro.css", _ => "/css/basic.css", }; - let pkg_version = env!("CARGO_PKG_VERSION"); - page.alter_assets(ContextOp::AddStyleSheet( + page.alter_assets(AssetsOp::AddStyleSheet( StyleSheet::from("/css/normalize.css") .with_version("8.0.1") .with_weight(-99), )) - .alter_assets(ContextOp::AddStyleSheet( - StyleSheet::from("/css/root.css") - .with_version(pkg_version) - .with_weight(-99), - )) - .alter_assets(ContextOp::AddStyleSheet( - StyleSheet::from("/css/components.css") - .with_version(pkg_version) - .with_weight(-99), - )) - .alter_assets(ContextOp::AddStyleSheet( - StyleSheet::from("/css/menu.css") - .with_version(pkg_version) - .with_weight(-99), - )) - .alter_assets(ContextOp::AddStyleSheet( + .alter_assets(AssetsOp::AddStyleSheet( StyleSheet::from(styles) - .with_version(pkg_version) - .with_weight(-99), - )) - .alter_assets(ContextOp::AddJavaScript( - JavaScript::defer("/js/menu.js") - .with_version(pkg_version) + .with_version(env!("CARGO_PKG_VERSION")) .with_weight(-99), )); } @@ -169,7 +148,7 @@ fn render_intro(page: &mut Page) -> Markup { } fn render_pagetop_intro(page: &mut Page) -> Markup { - page.alter_assets(ContextOp::AddJavaScript(JavaScript::on_load_async("intro-js", |cx| + page.alter_assets(AssetsOp::AddJavaScript(JavaScript::on_load_async("intro-js", |cx| util::indoc!(r#" try { const resp = await fetch("https://crates.io/api/v1/crates/pagetop"); diff --git a/src/core/action/all.rs b/src/core/action/all.rs index fbbf842..7fff970 100644 --- a/src/core/action/all.rs +++ b/src/core/action/all.rs @@ -5,12 +5,12 @@ use parking_lot::RwLock; use std::collections::HashMap; use std::sync::LazyLock; -// **< ACCIONES >*********************************************************************************** +// ACCIONES **************************************************************************************** static ACTIONS: LazyLock>> = LazyLock::new(|| RwLock::new(HashMap::new())); -// **< AÑADIR ACCIONES >**************************************************************************** +// AÑADIR ACCIONES ********************************************************************************* // Registra una nueva acción en el sistema. // @@ -36,7 +36,7 @@ pub fn add_action(action: ActionBox) { } } -// **< DESPLEGAR ACCIONES >************************************************************************* +// DESPLEGAR ACCIONES ****************************************************************************** /// Despacha y ejecuta las funciones asociadas a una [`ActionKey`]. /// diff --git a/src/core/component.rs b/src/core/component.rs index be9bbad..3691472 100644 --- a/src/core/component.rs +++ b/src/core/component.rs @@ -8,6 +8,5 @@ pub use children::Children; pub use children::{Child, ChildOp}; pub use children::{Typed, TypedOp}; -mod context; -pub use context::{Context, ContextError, ContextOp, Contextual}; -pub type FnPathByContext = fn(cx: &Context) -> &str; +mod slot; +pub use slot::TypedSlot; diff --git a/src/core/component/children.rs b/src/core/component/children.rs index c0c8841..cb112e1 100644 --- a/src/core/component/children.rs +++ b/src/core/component/children.rs @@ -1,6 +1,6 @@ -use crate::core::component::{Component, Context}; -use crate::html::{html, Markup}; -use crate::{builder_fn, AutoDefault, UniqueId}; +use crate::core::component::Component; +use crate::html::{html, Context, Markup}; +use crate::{builder_fn, UniqueId}; use parking_lot::RwLock; @@ -11,105 +11,76 @@ use std::vec::IntoIter; /// /// Esta estructura permite manipular y renderizar un componente que implemente [`Component`], y /// habilita acceso concurrente mediante [`Arc>`]. -#[derive(AutoDefault, Clone)] -pub struct Child(Option>>); +#[derive(Clone)] +pub struct Child(Arc>); impl Child { /// Crea un nuevo `Child` a partir de un componente. pub fn with(component: impl Component) -> Self { - Child(Some(Arc::new(RwLock::new(component)))) + Child(Arc::new(RwLock::new(component))) } - // **< Child BUILDER >************************************************************************** + // Child GETTERS ******************************************************************************* - /// Establece un componente nuevo, o lo vacía. - /// - /// Si se proporciona `Some(component)`, se encapsula como [`Child`]; y si es `None`, se limpia. - #[builder_fn] - pub fn with_component(mut self, component: Option) -> Self { - if let Some(c) = component { - self.0 = Some(Arc::new(RwLock::new(c))); - } else { - self.0 = None; - } - self - } - - // **< Child GETTERS >************************************************************************** - - /// Devuelve el identificador del componente, si existe y está definido. - #[inline] + /// Devuelve el identificador del componente, si está definido. pub fn id(&self) -> Option { - self.0.as_ref().and_then(|c| c.read().id()) + self.0.read().id() } - // **< Child RENDER >*************************************************************************** + // Child RENDER ******************************************************************************** /// Renderiza el componente con el contexto proporcionado. pub fn render(&self, cx: &mut Context) -> Markup { - self.0.as_ref().map_or(html! {}, |c| c.write().render(cx)) + self.0.write().render(cx) } - // **< Child HELPERS >************************************************************************** + // Child HELPERS ******************************************************************************* - // Devuelve el [`UniqueId`] del tipo del componente, si existe. - #[inline] - fn type_id(&self) -> Option { - self.0.as_ref().map(|c| c.read().type_id()) + // Devuelve el [`UniqueId`] del tipo del componente. + fn type_id(&self) -> UniqueId { + self.0.read().type_id() } } // ************************************************************************************************* -/// Variante tipada de [`Child`] para evitar conversiones de tipo durante el uso. +/// Variante tipada de [`Child`] para evitar conversiones durante el uso. /// /// Esta estructura permite manipular y renderizar un componente concreto que implemente /// [`Component`], y habilita acceso concurrente mediante [`Arc>`]. -#[derive(AutoDefault, Clone)] -pub struct Typed(Option>>); +pub struct Typed(Arc>); + +impl Clone for Typed { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} impl Typed { /// Crea un nuevo `Typed` a partir de un componente. pub fn with(component: C) -> Self { - Typed(Some(Arc::new(RwLock::new(component)))) + Typed(Arc::new(RwLock::new(component))) } - // **< Typed BUILDER >************************************************************************** + // Typed GETTERS ******************************************************************************* - /// Establece un componente nuevo, o lo vacía. - /// - /// Si se proporciona `Some(component)`, se encapsula como [`Typed`]; y si es `None`, se limpia. - #[builder_fn] - pub fn with_component(mut self, component: Option) -> Self { - self.0 = component.map(|c| Arc::new(RwLock::new(c))); - self - } - - // **< Typed GETTERS >************************************************************************** - - /// Devuelve el identificador del componente, si existe y está definido. - #[inline] + /// Devuelve el identificador del componente, si está definido. pub fn id(&self) -> Option { - self.0.as_ref().and_then(|c| c.read().id()) + self.0.read().id() } - // **< Typed RENDER >*************************************************************************** + // Typed RENDER ******************************************************************************** /// Renderiza el componente con el contexto proporcionado. pub fn render(&self, cx: &mut Context) -> Markup { - self.0.as_ref().map_or(html! {}, |c| c.write().render(cx)) + self.0.write().render(cx) } - // **< Typed HELPERS >************************************************************************** + // Typed HELPERS ******************************************************************************* // Convierte el componente tipado en un [`Child`]. - #[inline] fn into_child(self) -> Child { - if let Some(c) = &self.0 { - Child(Some(c.clone())) - } else { - Child(None) - } + Child(self.0.clone()) } } @@ -165,7 +136,7 @@ impl Children { opt } - // **< Children BUILDER >*********************************************************************** + // Children BUILDER **************************************************************************** /// Ejecuta una operación con [`ChildOp`] en la lista. #[builder_fn] @@ -204,7 +175,7 @@ impl Children { self } - // **< Children GETTERS >*********************************************************************** + // Children GETTERS **************************************************************************** /// Devuelve el número de componentes hijo de la lista. pub fn len(&self) -> usize { @@ -230,10 +201,10 @@ impl Children { /// Devuelve un iterador sobre los componentes hijo con el identificador de tipo ([`UniqueId`]) /// indicado. pub fn iter_by_type_id(&self, type_id: UniqueId) -> impl Iterator { - self.0.iter().filter(move |&c| c.type_id() == Some(type_id)) + self.0.iter().filter(move |&c| c.type_id() == type_id) } - // **< Children RENDER >************************************************************************ + // Children RENDER ***************************************************************************** /// Renderiza todos los componentes hijo, en orden. pub fn render(&self, cx: &mut Context) -> Markup { @@ -244,7 +215,7 @@ impl Children { } } - // **< Children HELPERS >*********************************************************************** + // Children HELPERS **************************************************************************** // Inserta un hijo después del componente con el `id` dado, o al final si no se encuentra. #[inline] diff --git a/src/core/component/definition.rs b/src/core/component/definition.rs index c0573b4..333cf69 100644 --- a/src/core/component/definition.rs +++ b/src/core/component/definition.rs @@ -1,7 +1,6 @@ use crate::base::action; -use crate::core::component::Context; use crate::core::{AnyInfo, TypeInfo}; -use crate::html::{html, Markup, PrepareMarkup}; +use crate::html::{html, Context, Markup, PrepareMarkup}; /// Define la función de renderizado para todos los componentes. /// diff --git a/src/core/component/slot.rs b/src/core/component/slot.rs new file mode 100644 index 0000000..19ed72a --- /dev/null +++ b/src/core/component/slot.rs @@ -0,0 +1,64 @@ +use crate::builder_fn; +use crate::core::component::{Component, Typed}; +use crate::html::{html, Context, Markup}; + +/// Contenedor para un componente [`Typed`] opcional. +/// +/// Un `TypedSlot` actúa como un contenedor dentro de otro componente para incluir o no un +/// subcomponente. Internamente encapsula `Option>`, pero proporciona una API más sencilla +/// para construir estructuras jerárquicas. +/// +/// # Ejemplo +/// +/// ```rust,ignore +/// use pagetop::prelude::*; +/// +/// let comp = MyComponent::new(); +/// let opt = TypedSlot::new(comp); +/// assert!(opt.get().is_some()); +/// ``` +pub struct TypedSlot(Option>); + +impl Default for TypedSlot { + fn default() -> Self { + TypedSlot(None) + } +} + +impl TypedSlot { + /// Crea un nuevo [`TypedSlot`]. + /// + /// El componente se envuelve automáticamente en un [`Typed`] y se almacena. + pub fn new(component: C) -> Self { + TypedSlot(Some(Typed::with(component))) + } + + // TypedSlot BUILDER ********************************************************************* + + /// Establece un componente nuevo, o lo vacía. + /// + /// Si se proporciona `Some(component)`, se guarda en [`Typed`]; y si es `None`, se limpia. + #[builder_fn] + pub fn with_value(mut self, component: Option) -> Self { + self.0 = component.map(Typed::with); + self + } + + // TypedSlot GETTERS ********************************************************************* + + /// Devuelve un clon (incrementa el contador `Arc`) de [`Typed`], si existe. + pub fn get(&self) -> Option> { + self.0.clone() + } + + // TypedSlot RENDER ************************************************************************ + + /// Renderiza el componente, si existe. + pub fn render(&self, cx: &mut Context) -> Markup { + if let Some(component) = &self.0 { + component.render(cx) + } else { + html! {} + } + } +} diff --git a/src/core/extension/all.rs b/src/core/extension/all.rs index fa67671..a243778 100644 --- a/src/core/extension/all.rs +++ b/src/core/extension/all.rs @@ -7,7 +7,7 @@ use parking_lot::RwLock; use std::sync::LazyLock; -// **< EXTENSIONES >******************************************************************************** +// EXTENSIONES ************************************************************************************* static ENABLED_EXTENSIONS: LazyLock>> = LazyLock::new(|| RwLock::new(Vec::new())); @@ -15,7 +15,7 @@ static ENABLED_EXTENSIONS: LazyLock>> = static DROPPED_EXTENSIONS: LazyLock>> = LazyLock::new(|| RwLock::new(Vec::new())); -// **< REGISTRO DE LAS EXTENSIONES >**************************************************************** +// REGISTRO DE LAS EXTENSIONES ********************************************************************* pub fn register_extensions(root_extension: Option) { // Prepara la lista de extensiones habilitadas. @@ -104,7 +104,7 @@ fn add_to_dropped(list: &mut Vec, extension: ExtensionRef) { } } -// **< REGISTRO DE LAS ACCIONES >******************************************************************* +// REGISTRO DE LAS ACCIONES ************************************************************************ pub fn register_actions() { for extension in ENABLED_EXTENSIONS.read().iter() { @@ -114,7 +114,7 @@ pub fn register_actions() { } } -// **< INICIALIZA LAS EXTENSIONES >***************************************************************** +// INICIALIZA LAS EXTENSIONES ********************************************************************** pub fn initialize_extensions() { trace::info!("Calling application bootstrap"); @@ -123,7 +123,7 @@ pub fn initialize_extensions() { } } -// **< CONFIGURA LOS SERVICIOS >******************************************************************** +// CONFIGURA LOS SERVICIOS ************************************************************************* pub fn configure_services(scfg: &mut service::web::ServiceConfig) { // Sólo compila durante el desarrollo, para evitar errores 400 en la traza de eventos. diff --git a/src/core/theme/all.rs b/src/core/theme/all.rs index 5e6b65b..ebb2848 100644 --- a/src/core/theme/all.rs +++ b/src/core/theme/all.rs @@ -5,11 +5,11 @@ use parking_lot::RwLock; use std::sync::LazyLock; -// **< TEMAS >************************************************************************************** +// TEMAS ******************************************************************************************* pub static THEMES: LazyLock>> = LazyLock::new(|| RwLock::new(Vec::new())); -// **< TEMA PREDETERMINADO >************************************************************************ +// TEMA PREDETERMINADO ***************************************************************************** pub static DEFAULT_THEME: LazyLock = LazyLock::new(|| match theme_by_short_name(&global::SETTINGS.app.theme) { @@ -17,7 +17,7 @@ pub static DEFAULT_THEME: LazyLock = None => &crate::base::theme::Basic, }); -// **< TEMA POR NOMBRE >**************************************************************************** +// TEMA POR NOMBRE ********************************************************************************* // Devuelve el tema identificado por su [`short_name()`](AnyInfo::short_name). pub fn theme_by_short_name(short_name: &'static str) -> Option { diff --git a/src/global.rs b/src/global.rs index c8805a3..ccc6d9d 100644 --- a/src/global.rs +++ b/src/global.rs @@ -50,11 +50,11 @@ pub struct App { pub theme: String, /// Idioma por defecto para la aplicación. /// - /// Si no está definido o no es válido, [`LangId`](crate::locale::LangId) determinará el idioma - /// efectivo para el renderizado en este orden: primero intentará usar el establecido mediante - /// [`Contextual::with_langid()`](crate::core::component::Contextual::with_langid); si no se ha - /// definido explícitamente, probará el indicado en la cabecera `Accept-Language` del navegador; - /// y, si ninguno aplica, se empleará el idioma de respaldo ("en-US"). + /// Si no está definido o no es válido, el idioma efectivo para el renderizado se resolverá + /// según la implementación de [`LangId`](crate::locale::LangId) en este orden: primero intenta + /// con el establecido en [`Contextual::with_langid()`](crate::html::Contextual::with_langid); + /// pero si no se ha definido explícitamente, usará el indicado en la cabecera `Accept-Language` + /// del navegador; y, si ninguno aplica, se empleará el idioma de respaldo ("en-US"). pub language: String, /// Banner ASCII mostrado al inicio: *"Off"* (desactivado), *"Slant"*, *"Small"*, *"Speed"* o /// *"Starwars"*. diff --git a/src/html.rs b/src/html.rs index a86c9f7..4858bbf 100644 --- a/src/html.rs +++ b/src/html.rs @@ -3,7 +3,7 @@ mod maud; pub use maud::{display, html, html_private, Escaper, Markup, PreEscaped, DOCTYPE}; -// **< HTML DOCUMENT ASSETS >*********************************************************************** +// HTML DOCUMENT ASSETS **************************************************************************** mod assets; pub use assets::favicon::Favicon; @@ -11,38 +11,12 @@ pub use assets::javascript::JavaScript; pub use assets::stylesheet::{StyleSheet, TargetMedia}; pub use assets::{Asset, Assets}; -// **< HTML DOCUMENT CONTEXT >********************************************************************** +// HTML DOCUMENT CONTEXT *************************************************************************** -/// **Obsoleto desde la versión 0.5.0**: usar [`core::component::Context`] en su lugar. -#[deprecated(since = "0.5.0", note = "Moved to `pagetop::core::component::Context`")] -pub type Context = crate::core::component::Context; +mod context; +pub use context::{AssetsOp, Context, Contextual, ErrorParam}; -/// **Obsoleto desde la versión 0.5.0**: usar [`core::component::ContextOp`] en su lugar. -#[deprecated( - since = "0.5.0", - note = "Moved to `pagetop::core::component::ContextOp`" -)] -pub type ContextOp = crate::core::component::ContextOp; - -/// **Obsoleto desde la versión 0.5.0**: usar [`core::component::Contextual`] en su lugar. -#[deprecated( - since = "0.5.0", - note = "Moved to `pagetop::core::component::Contextual`" -)] -pub trait Contextual: crate::core::component::Contextual {} - -/// **Obsoleto desde la versión 0.5.0**: usar [`core::component::ContextError`] en su lugar. -#[deprecated( - since = "0.5.0", - note = "Moved to `pagetop::core::component::ContextError`" -)] -pub type ContextError = crate::core::component::ContextError; - -/// **Obsoleto desde la versión 0.5.0**: usar [`ContextOp`] en su lugar. -#[deprecated(since = "0.5.0", note = "Use `ContextOp` instead")] -pub type AssetsOp = crate::core::component::ContextOp; - -// **< HTML ATTRIBUTES >**************************************************************************** +// HTML ATTRIBUTES ********************************************************************************* mod attr_id; pub use attr_id::AttrId; @@ -76,13 +50,14 @@ pub type OptionClasses = AttrClasses; use crate::{core, AutoDefault}; -/// **Obsoleto desde la versión 0.4.0**: usar [`Typed`](crate::core::component::Typed) en su lugar. +/// **Obsoleto desde la versión 0.4.0**: usar [`TypedSlot`](crate::core::component::TypedSlot) en su +/// lugar. #[deprecated( since = "0.4.0", - note = "Use `pagetop::core::component::Typed` instead" + note = "Use `pagetop::core::component::TypedSlot` instead" )] #[allow(type_alias_bounds)] -pub type OptionComponent = core::component::Typed; +pub type OptionComponent = core::component::TypedSlot; /// Prepara contenido HTML para su conversión a [`Markup`]. /// diff --git a/src/html/assets.rs b/src/html/assets.rs index fe5f5b7..41cd471 100644 --- a/src/html/assets.rs +++ b/src/html/assets.rs @@ -2,8 +2,7 @@ pub mod favicon; pub mod javascript; pub mod stylesheet; -use crate::core::component::Context; -use crate::html::{html, Markup}; +use crate::html::{html, Context, Markup}; use crate::{AutoDefault, Weight}; /// Representación genérica de un script [`JavaScript`](crate::html::JavaScript) o una hoja de diff --git a/src/html/assets/favicon.rs b/src/html/assets/favicon.rs index c2280aa..d731b8f 100644 --- a/src/html/assets/favicon.rs +++ b/src/html/assets/favicon.rs @@ -1,5 +1,4 @@ -use crate::core::component::Context; -use crate::html::{html, Markup}; +use crate::html::{html, Context, Markup}; use crate::AutoDefault; /// Un **Favicon** es un recurso gráfico que usa el navegador como icono asociado al sitio. @@ -53,7 +52,7 @@ impl Favicon { Favicon::default() } - // **< Favicon BUILDER >************************************************************************ + // Favicon BUILDER ***************************************************************************** /// Le añade un icono genérico apuntando a `image`. El tipo MIME se infiere automáticamente a /// partir de la extensión. @@ -153,8 +152,6 @@ impl Favicon { self } - // **< Favicon RENDER >************************************************************************* - /// Renderiza el **Favicon** completo con todas las etiquetas declaradas. /// /// El parámetro `Context` se acepta por coherencia con el resto de *assets*, aunque en este diff --git a/src/html/assets/javascript.rs b/src/html/assets/javascript.rs index dde5f94..a8ed3e8 100644 --- a/src/html/assets/javascript.rs +++ b/src/html/assets/javascript.rs @@ -1,6 +1,5 @@ -use crate::core::component::Context; use crate::html::assets::Asset; -use crate::html::{html, Markup, PreEscaped}; +use crate::html::{html, Context, Markup, PreEscaped}; use crate::{join, join_pair, AutoDefault, Weight}; // Define el origen del recurso JavaScript y cómo debe cargarse en el navegador. @@ -172,7 +171,7 @@ impl JavaScript { } } - // **< JavaScript BUILDER >********************************************************************* + // JavaScript BUILDER ************************************************************************** /// Asocia una **versión** al recurso (usada para control de la caché del navegador). /// @@ -211,8 +210,6 @@ impl Asset for JavaScript { self.weight } - // **< JavaScript RENDER >********************************************************************** - fn render(&self, cx: &mut Context) -> Markup { match &self.source { Source::From(path) => html! { diff --git a/src/html/assets/stylesheet.rs b/src/html/assets/stylesheet.rs index 49cb991..3ecc77f 100644 --- a/src/html/assets/stylesheet.rs +++ b/src/html/assets/stylesheet.rs @@ -1,6 +1,5 @@ -use crate::core::component::Context; use crate::html::assets::Asset; -use crate::html::{html, Markup, PreEscaped}; +use crate::html::{html, Context, Markup, PreEscaped}; use crate::{join_pair, AutoDefault, Weight}; // Define el origen del recurso CSS y cómo se incluye en el documento. @@ -114,7 +113,7 @@ impl StyleSheet { } } - // **< StyleSheet BUILDER >********************************************************************* + // StyleSheet BUILDER ************************************************************************** /// Asocia una versión al recurso (usada para control de la caché del navegador). /// @@ -133,7 +132,7 @@ impl StyleSheet { self } - // **< StyleSheet HELPERS >********************************************************************* + // StyleSheet EXTRAS *************************************************************************** /// Especifica el medio donde se aplican los estilos. /// @@ -164,8 +163,6 @@ impl Asset for StyleSheet { self.weight } - // **< StyleSheet RENDER >********************************************************************** - fn render(&self, cx: &mut Context) -> Markup { match &self.source { Source::From(path) => html! { diff --git a/src/html/attr_classes.rs b/src/html/attr_classes.rs index 80fdad7..098c26c 100644 --- a/src/html/attr_classes.rs +++ b/src/html/attr_classes.rs @@ -48,7 +48,7 @@ impl AttrClasses { AttrClasses::default().with_value(ClassesOp::Prepend, classes) } - // **< AttrClasses BUILDER >******************************************************************** + // AttrClasses BUILDER ************************************************************************* #[builder_fn] pub fn with_value(mut self, op: ClassesOp, classes: impl AsRef) -> Self { @@ -114,7 +114,7 @@ impl AttrClasses { } } - // **< AttrClasses GETTERS >******************************************************************** + // AttrClasses GETTERS ************************************************************************* /// Devuelve la cadena de clases, si existe. pub fn get(&self) -> Option { diff --git a/src/html/attr_id.rs b/src/html/attr_id.rs index 3d5f3eb..8bb1d33 100644 --- a/src/html/attr_id.rs +++ b/src/html/attr_id.rs @@ -29,7 +29,7 @@ impl AttrId { AttrId::default().with_value(value) } - // **< AttrId BUILDER >************************************************************************* + // AttrId BUILDER ****************************************************************************** /// Establece un identificador nuevo normalizando el valor. #[builder_fn] @@ -39,7 +39,7 @@ impl AttrId { self } - // **< AttrId GETTERS >************************************************************************* + // AttrId GETTERS ****************************************************************************** /// Devuelve el identificador normalizado, si existe. pub fn get(&self) -> Option { diff --git a/src/html/attr_l10n.rs b/src/html/attr_l10n.rs index 37fc80f..8250c74 100644 --- a/src/html/attr_l10n.rs +++ b/src/html/attr_l10n.rs @@ -39,7 +39,7 @@ impl AttrL10n { AttrL10n(value) } - // **< AttrL10n BUILDER >*********************************************************************** + // AttrL10n BUILDER **************************************************************************** /// Establece una traducción nueva. #[builder_fn] @@ -48,7 +48,7 @@ impl AttrL10n { self } - // **< AttrL10n GETTERS >*********************************************************************** + // AttrL10n GETTERS **************************************************************************** /// Devuelve la traducción para `language`, si existe. pub fn lookup(&self, language: &impl LangId) -> Option { diff --git a/src/html/attr_name.rs b/src/html/attr_name.rs index 9bc9659..928f841 100644 --- a/src/html/attr_name.rs +++ b/src/html/attr_name.rs @@ -29,7 +29,7 @@ impl AttrName { AttrName::default().with_value(value) } - // **< AttrName BUILDER >*********************************************************************** + // AttrName BUILDER **************************************************************************** /// Establece un nombre nuevo normalizando el valor. #[builder_fn] @@ -39,7 +39,7 @@ impl AttrName { self } - // **< AttrName GETTERS >*********************************************************************** + // AttrName GETTERS **************************************************************************** /// Devuelve el nombre normalizado, si existe. pub fn get(&self) -> Option { diff --git a/src/html/attr_value.rs b/src/html/attr_value.rs index eff8066..4e03120 100644 --- a/src/html/attr_value.rs +++ b/src/html/attr_value.rs @@ -27,7 +27,7 @@ impl AttrValue { AttrValue::default().with_value(value) } - // **< AttrValue BUILDER >********************************************************************** + // AttrValue BUILDER *************************************************************************** /// Establece una cadena nueva normalizando el valor. #[builder_fn] @@ -41,7 +41,7 @@ impl AttrValue { self } - // **< AttrValue GETTERS >********************************************************************** + // AttrValue GETTERS *************************************************************************** /// Devuelve la cadena normalizada, si existe. pub fn get(&self) -> Option { diff --git a/src/core/component/context.rs b/src/html/context.rs similarity index 85% rename from src/core/component/context.rs rename to src/html/context.rs index 9dad7f5..7b78268 100644 --- a/src/core/component/context.rs +++ b/src/html/context.rs @@ -10,8 +10,8 @@ use crate::{builder_fn, join}; use std::any::Any; use std::collections::HashMap; -/// Operaciones para modificar recursos asociados al contexto ([`Context`]) de un documento. -pub enum ContextOp { +/// Operaciones para modificar el contexto ([`Context`]) de un documento. +pub enum AssetsOp { // Favicon. /// Define el *favicon* del documento. Sobrescribe cualquier valor anterior. SetFavicon(Option), @@ -33,14 +33,14 @@ pub enum ContextOp { /// Errores de acceso a parámetros dinámicos del contexto. /// -/// - [`ContextError::ParamNotFound`]: la clave no existe. -/// - [`ContextError::ParamTypeMismatch`]: la clave existe, pero el valor guardado no coincide con -/// el tipo solicitado. Incluye nombre de la clave (`key`), tipo esperado (`expected`) y tipo -/// realmente guardado (`saved`) para facilitar el diagnóstico. +/// - [`ErrorParam::NotFound`]: la clave no existe. +/// - [`ErrorParam::TypeMismatch`]: la clave existe, pero el valor guardado no coincide con el tipo +/// solicitado. Incluye nombre de la clave (`key`), tipo esperado (`expected`) y tipo realmente +/// guardado (`saved`) para facilitar el diagnóstico. #[derive(Debug)] -pub enum ContextError { - ParamNotFound, - ParamTypeMismatch { +pub enum ErrorParam { + NotFound, + TypeMismatch { key: &'static str, expected: &'static str, saved: &'static str, @@ -55,12 +55,12 @@ pub enum ContextError { /// - Almacenar la **solicitud HTTP** de origen. /// - Seleccionar **tema** y **composición** (*layout*) de renderizado. /// - Administrar **recursos** del documento como el icono [`Favicon`], las hojas de estilo -/// [`StyleSheet`] o los scripts [`JavaScript`] mediante [`ContextOp`]. +/// [`StyleSheet`] o los scripts [`JavaScript`] mediante [`AssetsOp`]. /// - Leer y mantener **parámetros dinámicos tipados** de contexto. /// - Generar **identificadores únicos** por tipo de componente. /// /// Lo implementan, típicamente, estructuras que representan el contexto de renderizado, como -/// [`Context`](crate::core::component::Context) o [`Page`](crate::response::page::Page). +/// [`Context`](crate::html::Context) o [`Page`](crate::response::page::Page). /// /// # Ejemplo /// @@ -71,14 +71,14 @@ pub enum ContextError { /// cx.with_langid(&LangMatch::resolve("es-ES")) /// .with_theme("aliner") /// .with_layout("default") -/// .with_assets(ContextOp::SetFavicon(Some(Favicon::new().with_icon("/favicon.ico")))) -/// .with_assets(ContextOp::AddStyleSheet(StyleSheet::from("/css/app.css"))) -/// .with_assets(ContextOp::AddJavaScript(JavaScript::defer("/js/app.js"))) +/// .with_assets(AssetsOp::SetFavicon(Some(Favicon::new().with_icon("/favicon.ico")))) +/// .with_assets(AssetsOp::AddStyleSheet(StyleSheet::from("/css/app.css"))) +/// .with_assets(AssetsOp::AddJavaScript(JavaScript::defer("/js/app.js"))) /// .with_param("usuario_id", 42_i32) /// } /// ``` pub trait Contextual: LangId { - // **< Contextual BUILDER >********************************************************************* + // Contextual BUILDER ************************************************************************** /// Establece el idioma del documento. #[builder_fn] @@ -100,11 +100,11 @@ pub trait Contextual: LangId { #[builder_fn] fn with_param(self, key: &'static str, value: T) -> Self; - /// Define los recursos del contexto usando [`ContextOp`]. + /// Define los recursos del contexto usando [`AssetsOp`]. #[builder_fn] - fn with_assets(self, op: ContextOp) -> Self; + fn with_assets(self, op: AssetsOp) -> Self; - // **< Contextual GETTERS >********************************************************************* + // Contextual GETTERS ************************************************************************** /// Devuelve una referencia a la solicitud HTTP asociada, si existe. fn request(&self) -> Option<&HttpRequest>; @@ -142,7 +142,7 @@ pub trait Contextual: LangId { /// Devuelve los scripts JavaScript de los recursos del contexto. fn javascripts(&self) -> &Assets; - // **< Contextual HELPERS >********************************************************************* + // Contextual HELPERS ************************************************************************** /// Genera un identificador único por tipo (`-`) cuando no se aporta uno explícito. /// @@ -172,11 +172,11 @@ pub trait Contextual: LangId { /// // Selecciona un tema (por su nombre corto). /// .with_theme("aliner") /// // Asigna un favicon. -/// .with_assets(ContextOp::SetFavicon(Some(Favicon::new().with_icon("/favicon.ico")))) +/// .with_assets(AssetsOp::SetFavicon(Some(Favicon::new().with_icon("/favicon.ico")))) /// // Añade una hoja de estilo externa. -/// .with_assets(ContextOp::AddStyleSheet(StyleSheet::from("/css/style.css"))) +/// .with_assets(AssetsOp::AddStyleSheet(StyleSheet::from("/css/style.css"))) /// // Añade un script JavaScript. -/// .with_assets(ContextOp::AddJavaScript(JavaScript::defer("/js/main.js"))) +/// .with_assets(AssetsOp::AddJavaScript(JavaScript::defer("/js/main.js"))) /// // Añade un parámetro dinámico al contexto. /// .with_param("usuario_id", 42) /// } @@ -255,7 +255,7 @@ impl Context { } } - // **< Context RENDER >************************************************************************* + // Context RENDER ****************************************************************************** /// Renderiza los recursos del contexto. pub fn render_assets(&mut self) -> Markup { @@ -283,15 +283,15 @@ impl Context { markup } - // **< Context PARAMS >************************************************************************* + // Context PARAMS ****************************************************************************** /// Recupera una *referencia tipada* al parámetro solicitado. /// /// Devuelve: /// /// - `Ok(&T)` si la clave existe y el tipo coincide. - /// - `Err(ContextError::ParamNotFound)` si la clave no existe. - /// - `Err(ContextError::ParamTypeMismatch)` si la clave existe pero el tipo no coincide. + /// - `Err(ErrorParam::NotFound)` si la clave no existe. + /// - `Err(ErrorParam::TypeMismatch)` si la clave existe pero el tipo no coincide. /// /// # Ejemplos /// @@ -308,10 +308,10 @@ impl Context { /// // Error de tipo: /// assert!(cx.get_param::("usuario_id").is_err()); /// ``` - pub fn get_param(&self, key: &'static str) -> Result<&T, ContextError> { - let (any, type_name) = self.params.get(key).ok_or(ContextError::ParamNotFound)?; + pub fn get_param(&self, key: &'static str) -> Result<&T, ErrorParam> { + let (any, type_name) = self.params.get(key).ok_or(ErrorParam::NotFound)?; any.downcast_ref::() - .ok_or_else(|| ContextError::ParamTypeMismatch { + .ok_or_else(|| ErrorParam::TypeMismatch { key, expected: TypeInfo::FullName.of::(), saved: type_name, @@ -323,8 +323,8 @@ impl Context { /// Devuelve: /// /// - `Ok(T)` si la clave existía y el tipo coincide. - /// - `Err(ContextError::ParamNotFound)` si la clave no existe. - /// - `Err(ContextError::ParamTypeMismatch)` si el tipo no coincide. + /// - `Err(ErrorParam::NotFound)` si la clave no existe. + /// - `Err(ErrorParam::TypeMismatch)` si el tipo no coincide. /// /// # Ejemplos /// @@ -341,12 +341,12 @@ impl Context { /// // Error de tipo: /// assert!(cx.take_param::("titulo").is_err()); /// ``` - pub fn take_param(&mut self, key: &'static str) -> Result { - let (boxed, saved) = self.params.remove(key).ok_or(ContextError::ParamNotFound)?; + pub fn take_param(&mut self, key: &'static str) -> Result { + let (boxed, saved) = self.params.remove(key).ok_or(ErrorParam::NotFound)?; boxed .downcast::() .map(|b| *b) - .map_err(|_| ContextError::ParamTypeMismatch { + .map_err(|_| ErrorParam::TypeMismatch { key, expected: TypeInfo::FullName.of::(), saved, @@ -371,7 +371,7 @@ impl Context { } } -/// Permite a [`Context`](crate::core::component::Context) actuar como proveedor de idioma. +/// Permite a [`Context`](crate::html::Context) actuar como proveedor de idioma. /// /// Devuelve un [`LanguageIdentifier`] siguiendo este orden de prioridad: /// @@ -389,7 +389,7 @@ impl LangId for Context { } impl Contextual for Context { - // **< Contextual BUILDER >********************************************************************* + // Contextual BUILDER ************************************************************************** #[builder_fn] fn with_request(mut self, request: Option) -> Self { @@ -442,36 +442,36 @@ impl Contextual for Context { } #[builder_fn] - fn with_assets(mut self, op: ContextOp) -> Self { + fn with_assets(mut self, op: AssetsOp) -> Self { match op { // Favicon. - ContextOp::SetFavicon(favicon) => { + AssetsOp::SetFavicon(favicon) => { self.favicon = favicon; } - ContextOp::SetFaviconIfNone(icon) => { + AssetsOp::SetFaviconIfNone(icon) => { if self.favicon.is_none() { self.favicon = Some(icon); } } // Stylesheets. - ContextOp::AddStyleSheet(css) => { + AssetsOp::AddStyleSheet(css) => { self.stylesheets.add(css); } - ContextOp::RemoveStyleSheet(path) => { + AssetsOp::RemoveStyleSheet(path) => { self.stylesheets.remove(path); } // JavaScripts. - ContextOp::AddJavaScript(js) => { + AssetsOp::AddJavaScript(js) => { self.javascripts.add(js); } - ContextOp::RemoveJavaScript(path) => { + AssetsOp::RemoveJavaScript(path) => { self.javascripts.remove(path); } } self } - // **< Contextual GETTERS >********************************************************************* + // Contextual GETTERS ************************************************************************** fn request(&self) -> Option<&HttpRequest> { self.request.as_ref() @@ -530,7 +530,7 @@ impl Contextual for Context { &self.javascripts } - // **< Contextual HELPERS >********************************************************************* + // Contextual HELPERS ************************************************************************** /// Devuelve un identificador único dentro del contexto para el tipo `T`, si no se proporciona /// un `id` explícito. diff --git a/src/lib.rs b/src/lib.rs index 6f5c5cf..1c1ba2c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -97,7 +97,7 @@ extern crate self as pagetop; use std::collections::HashMap; use std::ops::Deref; -// **< RE-EXPORTED >******************************************************************************** +// RE-EXPORTED ************************************************************************************* pub use pagetop_macros::{builder_fn, html, main, test, AutoDefault}; @@ -136,7 +136,7 @@ pub type UniqueId = std::any::TypeId; /// antes en la ordenación. pub type Weight = i8; -// **< API >**************************************************************************************** +// API ********************************************************************************************* // Macros y funciones útiles. pub mod util; @@ -163,6 +163,6 @@ pub mod base; // Prepara y ejecuta la aplicación. pub mod app; -// **< PRELUDE >************************************************************************************ +// PRELUDE ***************************************************************************************** pub mod prelude; diff --git a/src/prelude.rs b/src/prelude.rs index a71375e..484e53c 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -28,14 +28,7 @@ pub use crate::global; pub use crate::trace; -// No se usa `pub use crate::html::*;` para evitar duplicar alias marcados como obsoletos -// (*deprecated*) porque han sido trasladados a `crate::core::component`. Cuando se retiren estos -// alias obsoletos se volverá a declarar como `pub use crate::html::*;`. -pub use crate::html::{ - display, html_private, Asset, Assets, AttrClasses, AttrId, AttrL10n, AttrName, AttrValue, - ClassesOp, Escaper, Favicon, JavaScript, Markup, PreEscaped, PrepareMarkup, StyleSheet, - TargetMedia, DOCTYPE, -}; +pub use crate::html::*; pub use crate::locale::*; diff --git a/src/response/page.rs b/src/response/page.rs index f81c980..2dc27f9 100644 --- a/src/response/page.rs +++ b/src/response/page.rs @@ -4,10 +4,11 @@ pub use error::ErrorPage; pub use actix_web::Result as ResultPage; use crate::base::action; -use crate::core::component::{Child, ChildOp, Component, Context, ContextOp, Contextual}; +use crate::core::component::{Child, ChildOp, Component}; use crate::core::theme::{ChildrenInRegions, ThemeRef, REGION_CONTENT}; use crate::html::{html, Markup, DOCTYPE}; use crate::html::{Assets, Favicon, JavaScript, StyleSheet}; +use crate::html::{AssetsOp, Context, Contextual}; use crate::html::{AttrClasses, ClassesOp}; use crate::html::{AttrId, AttrL10n}; use crate::locale::{CharacterDirection, L10n, LangId, LanguageIdentifier}; @@ -51,7 +52,7 @@ impl Page { } } - // **< Page BUILDER >*************************************************************************** + // Page BUILDER ******************************************************************************** /// Establece el título de la página como un valor traducible. #[builder_fn] @@ -150,7 +151,7 @@ impl Page { self } - // **< Page GETTERS >*************************************************************************** + // Page GETTERS ******************************************************************************** /// Devuelve el título traducido para el idioma de la página, si existe. pub fn title(&mut self) -> Option { @@ -191,7 +192,7 @@ impl Page { &mut self.context } - // **< Page RENDER >**************************************************************************** + // Page RENDER ********************************************************************************* /// Renderiza los componentes de una región (`region_name`) de la página. pub fn render_region(&mut self, region_name: &'static str) -> Markup { @@ -252,7 +253,7 @@ impl LangId for Page { } impl Contextual for Page { - // **< Contextual BUILDER >********************************************************************* + // Contextual BUILDER ************************************************************************** #[builder_fn] fn with_request(mut self, request: Option) -> Self { @@ -285,12 +286,12 @@ impl Contextual for Page { } #[builder_fn] - fn with_assets(mut self, op: ContextOp) -> Self { + fn with_assets(mut self, op: AssetsOp) -> Self { self.context.alter_assets(op); self } - // **< Contextual GETTERS >********************************************************************* + // Contextual GETTERS ************************************************************************** fn request(&self) -> Option<&HttpRequest> { self.context.request() @@ -320,7 +321,7 @@ impl Contextual for Page { self.context.javascripts() } - // **< Contextual HELPERS >********************************************************************* + // Contextual HELPERS ************************************************************************** fn required_id(&mut self, id: Option) -> String { self.context.required_id::(id) diff --git a/src/response/page/error.rs b/src/response/page/error.rs index 50e1c77..2355d23 100644 --- a/src/response/page/error.rs +++ b/src/response/page/error.rs @@ -1,5 +1,5 @@ use crate::base::component::Html; -use crate::core::component::Contextual; +use crate::html::Contextual; use crate::locale::L10n; use crate::response::ResponseError; use crate::service::http::{header::ContentType, StatusCode}; diff --git a/src/util.rs b/src/util.rs index a4daf67..cb10176 100644 --- a/src/util.rs +++ b/src/util.rs @@ -6,7 +6,7 @@ use std::env; use std::io; use std::path::{Path, PathBuf}; -// **< MACROS INTEGRADAS >************************************************************************** +// MACROS INTEGRADAS ******************************************************************************* #[doc(hidden)] pub use paste::paste; @@ -16,7 +16,7 @@ pub use concat_string::concat_string; pub use indoc::{concatdoc, formatdoc, indoc}; -// **< MACROS ÚTILES >****************************************************************************** +// MACROS ÚTILES *********************************************************************************** #[macro_export] /// Macro para construir una colección de pares clave-valor. @@ -198,7 +198,7 @@ macro_rules! join_strict { }}; } -// **< FUNCIONES ÚTILES >*************************************************************************** +// FUNCIONES ÚTILES ******************************************************************************** /// Resuelve y valida la ruta de un directorio existente, devolviendo una ruta absoluta. /// diff --git a/static/css/basic.css b/static/css/basic.css index 04801dd..312ddf0 100644 --- a/static/css/basic.css +++ b/static/css/basic.css @@ -3,3 +3,9 @@ .region--footer { padding-bottom: 2rem; } + +/* PoweredBy component */ + +.poweredby { + text-align: center; +} diff --git a/static/css/components.css b/static/css/components.css deleted file mode 100644 index ec5d3f0..0000000 --- a/static/css/components.css +++ /dev/null @@ -1,12 +0,0 @@ -/* Icon component */ - -.icon { - width: 1rem; - height: 1rem; -} - -/* PoweredBy component */ - -.poweredby { - text-align: center; -} diff --git a/static/css/menu.css b/static/css/menu.css deleted file mode 100644 index 5520e39..0000000 --- a/static/css/menu.css +++ /dev/null @@ -1,300 +0,0 @@ -.menu { - width: 100%; - height: auto; - margin: 0; - padding: 0; - z-index: 9999; - border: none; - outline: none; - background: var(--val-menu--color-bg); -} - -.menu__wrapper { - padding-right: var(--val-gap); -} -.menu__wrapper a, -.menu__wrapper button { - cursor: pointer; - border: none; - background: none; - text-decoration: none; -} - -.menu__nav ul { - margin: 0; - padding: 0; -} -.menu__nav li { - display: inline-block; - margin: 0 0 0 1.5rem; - padding: 0; - line-height: var(--val-menu--item-height); - list-style: none; - list-style-type: none; -} - -.menu__item--label, -.menu__nav li > a { - position: relative; - font-weight: 500; - color: var(--val-color--text); - text-rendering: optimizeLegibility; -} -.menu__nav li > a { - border: none; - transition: color 0.3s ease-in-out; -} -.menu__nav li:hover > a, -.menu__nav li > a:focus { - color: var(--val-menu--color-highlight); -} -.menu__nav li > a > svg.icon { - margin-left: 0.25rem; -} - -.menu__children { - position: absolute; - max-width: 100%; - height: auto; - padding: var(--val-gap-0-5) var(--val-gap-1-5); - border: none; - outline: none; - background: var(--val-menu--color-bg); - border-top: 3px solid var(--val-menu--color-highlight); - z-index: 500; - opacity: 0; - visibility: hidden; - box-shadow: 0 4px 6px -1px var(--val-menu--color-border), 0 2px 4px -1px var(--val-menu--color-shadow); - transition: all 0.5s ease-in-out; -} - -.menu__item--children:hover > .menu__children, -.menu__item--children > a:focus + .menu__children, -.menu__item--children .menu__children:focus-within { - margin-top: 0.4rem; - opacity: 1; - visibility: visible; -} - -.menu__submenu { - min-width: var(--val-menu--item-width-min); - max-width: var(--val-menu--item-width-max); -} -.menu__submenu-title { - font-family: inherit; - font-size: 1rem; - font-weight: 500; - margin: 0; - padding: var(--val-menu--line-padding) 0; - line-height: var(--val-menu--line-height); - border: none; - outline: none; - color: var(--val-menu--color-highlight); - text-transform: uppercase; - text-rendering: optimizeLegibility; -} -.menu__submenu li { - display: block; - margin: 0; -} - -.menu__children--mega { - left: 50%; - transform: translateX(-50%); -} - -.menu__mega { - display: flex; - flex-wrap: nowrap; -} - -.menu__header, -.menu__trigger { - display: none; -} - -/* Applies <= 992px */ -@media only screen and (max-width: 62rem) { - .menu__wrapper { - padding-right: var(--val-gap-0-5); - } - .menu__trigger { - cursor: pointer; - width: var(--val-menu--trigger-width); - height: var(--val-menu--item-height); - border: none; - outline: none; - background: none; - display: flex; - flex-direction: column; - justify-content: center; - } - .menu__trigger svg.icon { - width: 2rem; - height: 2rem; - } - .menu__nav { - position: fixed; - top: 0; - left: 0; - width: var(--val-menu--side-width); - height: 100%; - z-index: 9099; - overflow: hidden; - background: var(--val-menu--color-bg); - transform: translate(-100%); - transition: all 0.5s ease-in-out; - } - .menu__nav.active { - transform: translate(0%); - } - - .menu__nav li { - display: block; - margin: 0; - line-height: var(--val-menu--line-height); - } - - .menu__item--label, - .menu__nav li > a { - display: block; - padding: var(--val-menu--line-padding) var(--val-menu--item-height) var(--val-menu--line-padding) var(--val-menu--item-gap); - border-bottom: 1px solid var(--val-menu--color-border); - } - .menu__nav li ul li.menu__item--label, - .menu__nav li ul li > a { - border-bottom: 0; - } - .menu__nav li > a > svg.icon { - position: absolute; - top: var(--val-menu--line-padding); - right: var(--val-menu--line-padding); - height: var(--val-menu--line-height); - font-size: 1.25rem; - transform: rotate(-90deg); - } - - .menu__children { - position: absolute; - display: none; - top: 0; - left: 0; - max-width: none; - min-width: auto; - width: 100%; - height: 100%; - margin: 0 !important; - padding: 0; - border-top: 0; - opacity: 1; - overflow-y: auto; - visibility: visible; - transform: translateX(0%); - box-shadow: none; - } - .menu__children.active { - display: block; - } - .menu__children > :first-child { - margin-top: 2.675rem; - } - - .menu__submenu-title { - padding: var(--val-menu--line-padding) var(--val-menu--item-height) var(--val-menu--line-padding) var(--val-menu--item-gap); - } - - .menu__mega { - display: block; - } - - .menu__header { - position: sticky; - display: flex; - align-items: center; - justify-content: space-between; - top: 0; - height: var(--val-menu--item-height); - border-bottom: 1px solid var(--val-menu--color-border); - background: var(--val-menu--color-bg); - z-index: 501; - } - .menu__title { - padding: var(--val-menu--line-padding); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - .menu__close, - .menu__back { - width: var(--val-menu--item-height); - min-width: var(--val-menu--item-height); - height: var(--val-menu--item-height); - line-height: var(--val-menu--item-height); - color: var(--val-color--text); - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - } - .menu__close { - font-size: 2.25rem; - border-left: 1px solid var(--val-menu--color-border) !important; - } - .menu__back { - font-size: 1.25rem; - border-right: 1px solid var(--val-menu--color-border) !important; - display: none; - } - .menu__header.active .menu__back { - display: flex; - } - - .menu__list { - height: 100%; - overflow-y: auto; - overflow-x: hidden; - padding: 0; - margin: 0; - } - - .menu__overlay { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 9098; - opacity: 0; - visibility: hidden; - background: rgba(0, 0, 0, 0.55); - transition: all 0.5s ease-in-out; - } - .menu__overlay.active { - opacity: 1; - visibility: visible; - } -} - -/* ANIMATIONS */ - -@keyframes slideLeft { - 0% { - opacity: 0; - transform: translateX(100%); - } - 100% { - opacity: 1; - transform: translateX(0%); - } -} - -@keyframes slideRight { - 0% { - opacity: 1; - transform: translateX(0%); - } - 100% { - opacity: 0; - transform: translateX(100%); - } -} diff --git a/static/css/root.css b/static/css/root.css deleted file mode 100644 index aeab1c6..0000000 --- a/static/css/root.css +++ /dev/null @@ -1,212 +0,0 @@ -:root { - --val-font-sans: system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"; - --val-font-serif: "Lora","georgia",serif; - --val-font-monospace: SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace; - --val-font-family: var(--val-font-sans); - - /* Font size */ - --val-fs--x3l: 2.5rem; - --val-fs--x2l: 2rem; - --val-fs--xl: 1.75rem; - --val-fs--l: 1.5rem; - --val-fs--m: 1.25rem; - --val-fs--base: 1rem; - --val-fs--s: 0.875rem; - --val-fs--xs: 0.75rem; - --val-fs--x2s: 0.5625rem; - --val-fs--x3s: 0.375rem; - - /* Font weight */ - --val-fw--light: 300; - --val-fw--base: 400; - --val-fw--bold: 500; - - /* Line height */ - --val-lh--base: 1.5; - --val-lh--header: 1.2; - - --val-max-width: 90rem; -/* - --val-color-rgb: 33,37,41; - --val-main--bg-rgb: 255,255,255; - --val-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0)); - - --line-height-base: 1.6875rem; - --line-height-s: 1.125rem; - --max-bg-color: 98.125rem; -*/ - --val-gap: 1.125rem; -/* - --content-left: 5.625rem; - --site-header-height-wide: var(--val-gap10); - --container-padding: var(--val-gap); -*/ -} -/* -@media (min-width: 75rem) { - :root { - --container-padding:var(--val-gap2); - } -} - -:root { - --scrollbar-width: 0px; - --grid-col-count: 6; - --grid-gap: var(--val-gap); - --grid-gap-count: calc(var(--grid-col-count) - 1); - --grid-full-width: calc(100vw - var(--val-gap2) - var(--scrollbar-width)); - --grid-col-width: calc((var(--grid-full-width) - (var(--grid-gap-count) * var(--grid-gap))) / var(--grid-col-count)); -} - -@media (min-width: 43.75rem) { - :root { - --grid-col-count:14; - --grid-gap: var(--val-gap2); - } -} - -@media (min-width: 62.5rem) { - :root { - --scrollbar-width:0.9375rem; - } -} - -@media (min-width: 75rem) { - :root { - --grid-full-width:calc(100vw - var(--scrollbar-width) - var(--content-left) - var(--val-gap4)); - } -} - -@media (min-width: 90rem) { - :root { - --grid-full-width:calc(var(--max-width) - var(--val-gap4)); - } -} -*/ -:root { - --val-gap-0-15: calc(0.15 * var(--val-gap)); - --val-gap-0-25: calc(0.25 * var(--val-gap)); - --val-gap-0-35: calc(0.35 * var(--val-gap)); - --val-gap-0-5: calc(0.5 * var(--val-gap)); - --val-gap-0-75: calc(0.75 * var(--val-gap)); - --val-gap-1-5: calc(1.5 * var(--val-gap)); - --val-gap-2: calc(2 * var(--val-gap)); - - --primary-hue: 216; - --primary-sat: 60%; - --val-color--primary: hsl(var(--primary-hue), var(--primary-sat), 50%); - --val-color--primary-light: hsl(var(--primary-hue), var(--primary-sat), 60%); - --val-color--primary-dark: hsl(var(--primary-hue), var(--primary-sat), 40%); - --val-color--primary-link: hsl(var(--primary-hue), var(--primary-sat), 55%); - --val-color--primary-link-hover: hsl(var(--primary-hue), var(--primary-sat), 30%); - --val-color--primary-link-active: hsl(var(--primary-hue), var(--primary-sat), 70%); - - --info-hue: 190; - --info-sat: 90%; - --val-color--info: hsl(var(--info-hue), var(--info-sat), 54%); - --val-color--info-light: hsl(var(--info-hue), var(--info-sat), 70%); - --val-color--info-dark: hsl(var(--info-hue), var(--info-sat), 45%); - --val-color--info-link: hsl(var(--info-hue), var(--info-sat), 30%); - --val-color--info-link-hover: hsl(var(--info-hue), var(--info-sat), 20%); - --val-color--info-link-active: hsl(var(--info-hue), var(--info-sat), 40%); - - --success-hue: 150; - --success-sat: 50%; - --val-color--success: hsl(var(--success-hue), var(--success-sat), 50%); - --val-color--success-light: hsl(var(--success-hue), var(--success-sat), 68%); - --val-color--success-dark: hsl(var(--success-hue), var(--success-sat), 38%); - --val-color--success-link: hsl(var(--success-hue), var(--success-sat), 26%); - --val-color--success-link-hover: hsl(var(--success-hue), var(--success-sat), 18%); - --val-color--success-link-active: hsl(var(--success-hue), var(--success-sat), 36%); - - --warning-hue: 44; - --warning-sat: 100%; - --val-color--warning: hsl(var(--warning-hue), var(--warning-sat), 50%); - --val-color--warning-light: hsl(var(--warning-hue), var(--warning-sat), 60%); - --val-color--warning-dark: hsl(var(--warning-hue), var(--warning-sat), 40%); - --val-color--warning-link: hsl(var(--warning-hue), var(--warning-sat), 30%); - --val-color--warning-link-hover: hsl(var(--warning-hue), var(--warning-sat), 20%); - --val-color--warning-link-active: hsl(var(--warning-hue), var(--warning-sat), 38%); - - --danger-hue: 348; - --danger-sat: 86%; - --val-color--danger: hsl(var(--danger-hue), var(--danger-sat), 50%); - --val-color--danger-light: hsl(var(--danger-hue), var(--danger-sat), 60%); - --val-color--danger-dark: hsl(var(--danger-hue), var(--danger-sat), 35%); - --val-color--danger-link: hsl(var(--danger-hue), var(--danger-sat), 25%); - --val-color--danger-link-hover: hsl(var(--danger-hue), var(--danger-sat), 10%); - --val-color--danger-link-active: hsl(var(--danger-hue), var(--danger-sat), 30%); - - --light-hue: 0; - --light-sat: 0%; - --val-color--light: hsl(var(--light-hue), var(--light-sat), 96%); - --val-color--light-light: hsl(var(--light-hue), var(--light-sat), 98%); - --val-color--light-dark: hsl(var(--light-hue), var(--light-sat), 92%); - - --dark-hue: 0; - --dark-sat: 0%; - --val-color--dark: hsl(var(--dark-hue), var(--dark-sat), 25%); - --val-color--dark-light: hsl(var(--dark-hue), var(--dark-sat), 40%); - --val-color--dark-dark: hsl(var(--dark-hue), var(--dark-sat), 8%); - --val-color--dark-link: hsl(var(--dark-hue), var(--dark-sat), 90%); - --val-color--dark-link-hover: hsl(var(--dark-hue), var(--dark-sat), 100%); - --val-color--dark-link-active: hsl(var(--dark-hue), var(--dark-sat), 70%); - - - - - --gray-hue: 201; - --gray-sat: 15%; - --val-color--gray-5: hsl(var(--gray-hue), var(--gray-sat), 5%); - --val-color--gray-10: hsl(var(--gray-hue), var(--gray-sat) ,11%); - --val-color--gray-20: hsl(var(--gray-hue), var(--gray-sat),20%); - --val-color--gray-45: hsl(var(--gray-hue), var(--gray-sat), 44%); - --val-color--gray-60: hsl(var(--gray-hue), var(--gray-sat), 57%); - --val-color--gray-65: hsl(var(--gray-hue), var(--gray-sat), 63%); - --val-color--gray-70: hsl(var(--gray-hue), var(--gray-sat), 72%); - --val-color--gray-90: hsl(var(--gray-hue), var(--gray-sat), 88%); - --val-color--gray-95: hsl(var(--gray-hue), var(--gray-sat), 93%); - --val-color--gray-100: hsl(var(--gray-hue), var(--gray-sat), 97%); - - - - - --val-color--bg: #fafafa; - --val-color--text: #212529; - --val-color--white: #fff; - -/* - - - --color-text-neutral-soft: var(--color--gray-45); - --color-text-neutral-medium: var(--color--gray-20); - --color-text-neutral-loud: var(--color--gray-5); - --color-text-primary-medium: var(--val-color--primary-40); - --color-text-primary-loud: var(--val-color--primary-30); - --color--black: #000; -*/ -/* - --color--red: #e33f1e; - --color--gold: #fdca40; - --color--green: #3fa21c; - --header-height-wide-when-fixed: calc(6 * var(--val-gap)); - --mobile-nav-width: 31.25rem; - - --val-menu--border-radius: 0.625rem; -*/ - --val-border-radius: 0.375rem; - - /* Menu component */ - --val-menu--color-bg: var(--val-color--bg); - --val-menu--color-highlight: #e91e63; - --val-menu--color-border: rgba(0, 0, 0, 0.1); - --val-menu--color-shadow: rgba(0, 0, 0, 0.06); - --val-menu--line-padding: 0.625rem; - --val-menu--line-height: calc(1.875rem + 1px); - --val-menu--item-height: calc(var(--val-menu--line-padding) + var(--val-menu--line-height)); - --val-menu--item-width-min: 14rem; - --val-menu--item-width-max: 20rem; - --val-menu--item-gap: 1rem; - --val-menu--trigger-width: 2.675rem; - --val-menu--side-width: 20rem; -} diff --git a/static/js/menu.js b/static/js/menu.js deleted file mode 100644 index 6b5ae1b..0000000 --- a/static/js/menu.js +++ /dev/null @@ -1,97 +0,0 @@ -function menu__showChildren(nav, children) { - const li = children[0]; - const submenu = li.querySelector('.menu__children'); - submenu.classList.add('active'); - submenu.style.animation = 'slideLeft 0.5s ease forwards'; - - const labelEl = li.querySelector('.menu__label'); - const title = labelEl ? labelEl.textContent.trim() : (li.querySelector('a')?.textContent?.trim() ?? ''); - nav.querySelector('.menu__title').innerHTML = title; - nav.querySelector('.menu__header').classList.add('active'); -} - -function menu__hideChildren(nav, children) { - const submenu = children[0].querySelector('.menu__children'); - submenu.style.animation = 'slideRight 0.5s ease forwards'; - setTimeout(() => { - submenu.classList.remove('active'); - submenu.style.removeProperty('animation'); - }, 300); - - children.shift(); - if (children.length > 0) { - const a = children[0].querySelector('a'); - const title = (a && a.textContent ? a.textContent.trim() : ''); - nav.querySelector('.menu__title').textContent = title; - } else { - nav.querySelector('.menu__header').classList.remove('active'); - nav.querySelector('.menu__title').textContent = ''; - } -} - -function menu__toggle(nav, overlay) { - nav.classList.toggle('active'); - overlay.classList.toggle('active'); -} - -function menu__reset(menu, nav, overlay) { - menu__toggle(nav, overlay); - setTimeout(() => { - nav.querySelector('.menu__header').classList.remove('active'); - nav.querySelector('.menu__title').innerHTML = ''; - menu.querySelectorAll('.menu__children').forEach(submenu => { - submenu.classList.remove('active'); - submenu.style.removeProperty('animation'); - }); - }, 300); - return []; -} - -document.querySelectorAll('.menu').forEach(menu => { - - let menuChildren = []; - const menuNav = menu.querySelector('.menu__nav'); - const menuOverlay = menu.querySelector('.menu__overlay'); - - menu.querySelector('.menu__list').addEventListener('click', (e) => { - if (menuNav.classList.contains('active')) { - let target = e.target.closest('.menu__item--children'); - if (target && target != menuChildren[0]) { - menuChildren.unshift(target); - menu__showChildren(menuNav, menuChildren); - } - } - }); - - menu.querySelector('.menu__back').addEventListener('click', () => { - menu__hideChildren(menuNav, menuChildren); - }); - - menu.querySelector('.menu__close').addEventListener('click', () => { - menuChildren = menu__reset(menu, menuNav, menuOverlay); - }); - - menu.querySelectorAll('.menu__item--link > a[target="_blank"]').forEach(link => { - link.addEventListener('click', (e) => { - menuChildren = menu__reset(menu, menuNav, menuOverlay); - e.target.blur(); - }); - }); - - menu.querySelector('.menu__trigger').addEventListener('click', () => { - menu__toggle(menuNav, menuOverlay); - }); - - menuOverlay.addEventListener('click', () => { - menu__toggle(menuNav, menuOverlay); - }); - - window.onresize = function () { - if (menuNav.classList.contains('active')) { - var fontSizeRoot = parseFloat(getComputedStyle(document.documentElement).fontSize); - if (this.innerWidth >= 62 * fontSizeRoot) { - menuChildren = menu__reset(menu, menuNav, menuOverlay); - } - } - }; -}); diff --git a/tests/component_poweredby.rs b/tests/component_poweredby.rs index 27683d9..e4551d1 100644 --- a/tests/component_poweredby.rs +++ b/tests/component_poweredby.rs @@ -90,7 +90,7 @@ async fn poweredby_getter_reflects_internal_state() { assert!(c1.contains(&global::SETTINGS.app.name)); } -// **< HELPERS >************************************************************************************ +// HELPERS ***************************************************************************************** fn render_component(c: &C) -> Markup { let mut cx = Context::default();