♻️ Major code restructuring

This commit is contained in:
Manuel Cillero 2024-02-09 14:05:38 +01:00
parent a96e203bb3
commit fa66d628a0
221 changed files with 228 additions and 315 deletions

10
src/core/action.rs Normal file
View file

@ -0,0 +1,10 @@
mod definition;
pub use definition::{action_ref, ActionBase, ActionTrait};
mod list;
pub use list::Action;
use list::ActionsList;
mod all;
pub(crate) use all::add_action;
pub use all::{dispatch_actions, KeyAction};

34
src/core/action/all.rs Normal file
View file

@ -0,0 +1,34 @@
use crate::core::action::{Action, ActionsList};
use crate::{Handle, LazyStatic};
use std::collections::HashMap;
use std::sync::RwLock;
pub type KeyAction = (Handle, Option<Handle>, Option<String>);
// Registered actions.
static ACTIONS: LazyStatic<RwLock<HashMap<KeyAction, ActionsList>>> =
LazyStatic::new(|| RwLock::new(HashMap::new()));
pub fn add_action(action: Action) {
let mut actions = ACTIONS.write().unwrap();
let key_action = (
action.handle(),
action.referer_handle(),
action.referer_id(),
);
if let Some(list) = actions.get_mut(&key_action) {
list.add(action);
} else {
actions.insert(key_action, ActionsList::new(action));
}
}
pub fn dispatch_actions<B, F>(key_action: KeyAction, f: F)
where
F: FnMut(&Action) -> B,
{
if let Some(list) = ACTIONS.read().unwrap().get(&key_action) {
list.iter_map(f)
}
}

View file

@ -0,0 +1,31 @@
use crate::{Handle, ImplementHandle, Weight};
use std::any::Any;
pub trait ActionBase: Any {
fn as_ref_any(&self) -> &dyn Any;
}
pub trait ActionTrait: ActionBase + ImplementHandle + Send + Sync {
fn referer_handle(&self) -> Option<Handle> {
None
}
fn referer_id(&self) -> Option<String> {
None
}
fn weight(&self) -> Weight {
0
}
}
impl<C: ActionTrait> ActionBase for C {
fn as_ref_any(&self) -> &dyn Any {
self
}
}
pub fn action_ref<A: 'static>(action: &dyn ActionTrait) -> &A {
action.as_ref_any().downcast_ref::<A>().unwrap()
}

45
src/core/action/list.rs Normal file
View file

@ -0,0 +1,45 @@
use crate::core::action::ActionTrait;
use crate::SmartDefault;
use std::sync::{Arc, RwLock};
pub type Action = Box<dyn ActionTrait>;
#[derive(SmartDefault)]
pub struct ActionsList(Arc<RwLock<Vec<Action>>>);
impl ActionsList {
pub fn new(action: Action) -> Self {
let mut list = ActionsList::default();
list.add(action);
list
}
pub fn add(&mut self, action: Action) {
let mut list = self.0.write().unwrap();
list.push(action);
list.sort_by_key(|a| a.weight());
}
pub fn iter_map<B, F>(&self, f: F)
where
Self: Sized,
F: FnMut(&Action) -> B,
{
let _: Vec<_> = self.0.read().unwrap().iter().map(f).collect();
}
}
#[macro_export]
macro_rules! actions {
() => {
Vec::<Action>::new()
};
( $($action:expr),+ $(,)? ) => {{
let mut v = Vec::<Action>::new();
$(
v.push(Box::new($action));
)*
v
}};
}

20
src/core/component.rs Normal file
View file

@ -0,0 +1,20 @@
mod context;
pub use context::{Context, ContextOp};
pub type FnContextualPath = fn(cx: &Context) -> &str;
mod renderable;
pub use renderable::{FnIsRenderable, Renderable};
mod definition;
pub use definition::{component_as_mut, component_as_ref, ComponentBase, ComponentTrait};
mod classes;
pub use classes::{ImplementClasses, ImplementClassesOp};
mod arc_any;
pub use arc_any::AnyComponents;
pub use arc_any::{ArcAnyComponent, ArcAnyOp};
mod arc_typed;
pub use arc_typed::TypedComponents;
pub use arc_typed::{ArcTypedComponent, ArcTypedOp};

View file

@ -0,0 +1,144 @@
use crate::core::component::{ComponentTrait, Context};
use crate::html::{html, Markup};
use crate::{fn_with, Handle, Weight};
use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
#[derive(Clone)]
pub struct ArcAnyComponent(Arc<RwLock<dyn ComponentTrait>>);
impl ArcAnyComponent {
pub fn new(component: impl ComponentTrait) -> Self {
ArcAnyComponent(Arc::new(RwLock::new(component)))
}
// ArcAnyComponent BUILDER.
pub fn set(&mut self, component: impl ComponentTrait) {
self.0 = Arc::new(RwLock::new(component));
}
// ArcAnyComponent GETTERS.
pub fn get(&self) -> RwLockReadGuard<'_, dyn ComponentTrait> {
self.0.read().unwrap()
}
pub fn get_mut(&self) -> RwLockWriteGuard<'_, dyn ComponentTrait> {
self.0.write().unwrap()
}
// ArcAnyComponent RENDER.
pub fn render(&self, cx: &mut Context) -> Markup {
self.0.write().unwrap().render(cx)
}
// ArcAnyComponent HELPERS.
fn handle(&self) -> Handle {
self.0.read().unwrap().handle()
}
fn id(&self) -> String {
self.0.read().unwrap().id().unwrap_or_default()
}
fn weight(&self) -> Weight {
self.0.read().unwrap().weight()
}
}
// *************************************************************************************************
pub enum ArcAnyOp {
Add(ArcAnyComponent),
AddAfterId(&'static str, ArcAnyComponent),
AddBeforeId(&'static str, ArcAnyComponent),
Prepend(ArcAnyComponent),
RemoveById(&'static str),
ReplaceById(&'static str, ArcAnyComponent),
Reset,
}
#[derive(Clone, Default)]
pub struct AnyComponents(Vec<ArcAnyComponent>);
impl AnyComponents {
pub fn new(arc: ArcAnyComponent) -> Self {
AnyComponents::default().with_value(ArcAnyOp::Add(arc))
}
pub(crate) fn merge(mixes: &[Option<&AnyComponents>]) -> Self {
let mut opt = AnyComponents::default();
for m in mixes.iter().flatten() {
opt.0.append(&mut m.0.clone());
}
opt
}
// AnyComponents BUILDER.
#[fn_with]
pub fn alter_value(&mut self, op: ArcAnyOp) -> &mut Self {
match op {
ArcAnyOp::Add(arc) => self.0.push(arc),
ArcAnyOp::AddAfterId(id, arc) => match self.0.iter().position(|c| c.id() == id) {
Some(index) => self.0.insert(index + 1, arc),
_ => self.0.push(arc),
},
ArcAnyOp::AddBeforeId(id, arc) => match self.0.iter().position(|c| c.id() == id) {
Some(index) => self.0.insert(index, arc),
_ => self.0.insert(0, arc),
},
ArcAnyOp::Prepend(arc) => self.0.insert(0, arc),
ArcAnyOp::RemoveById(id) => {
if let Some(index) = self.0.iter().position(|c| c.id() == id) {
self.0.remove(index);
}
}
ArcAnyOp::ReplaceById(id, arc) => {
for c in self.0.iter_mut() {
if c.id() == id {
*c = arc;
break;
}
}
}
ArcAnyOp::Reset => self.0.clear(),
}
self
}
// AnyComponents GETTERS.
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn get_by_id(&self, id: impl Into<String>) -> Option<&ArcAnyComponent> {
let id = id.into();
self.0.iter().find(|c| c.id() == id)
}
pub fn iter_by_id(&self, id: impl Into<String>) -> impl Iterator<Item = &ArcAnyComponent> {
let id = id.into();
self.0.iter().filter(move |&c| c.id() == id)
}
pub fn iter_by_handle(&self, handle: Handle) -> impl Iterator<Item = &ArcAnyComponent> {
self.0.iter().filter(move |&c| c.handle() == handle)
}
// AnyComponents RENDER.
pub fn render(&self, cx: &mut Context) -> Markup {
let mut components = self.0.clone();
components.sort_by_key(|c| c.weight());
html! {
@for c in components.iter() {
" " (c.render(cx)) " "
}
}
}
}

View file

@ -0,0 +1,141 @@
use crate::core::component::{ComponentTrait, Context};
use crate::html::{html, Markup};
use crate::{fn_with, Handle, Weight};
use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
pub struct ArcTypedComponent<C: ComponentTrait>(Arc<RwLock<C>>);
impl<C: ComponentTrait> Clone for ArcTypedComponent<C> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<C: ComponentTrait> ArcTypedComponent<C> {
pub fn new(component: C) -> Self {
ArcTypedComponent(Arc::new(RwLock::new(component)))
}
// ArcTypedComponent BUILDER.
pub fn set(&mut self, component: C) {
self.0 = Arc::new(RwLock::new(component));
}
// ArcTypedComponent GETTERS.
pub fn get(&self) -> RwLockReadGuard<'_, C> {
self.0.read().unwrap()
}
pub fn get_mut(&self) -> RwLockWriteGuard<'_, C> {
self.0.write().unwrap()
}
// ArcTypedComponent RENDER.
pub fn render(&self, cx: &mut Context) -> Markup {
self.0.write().unwrap().render(cx)
}
// ArcTypedComponent HELPERS.
fn handle(&self) -> Handle {
self.0.read().unwrap().handle()
}
fn id(&self) -> String {
self.0.read().unwrap().id().unwrap_or_default()
}
fn weight(&self) -> Weight {
self.0.read().unwrap().weight()
}
}
// *************************************************************************************************
pub enum ArcTypedOp<C: ComponentTrait> {
Add(ArcTypedComponent<C>),
AddAfterId(&'static str, ArcTypedComponent<C>),
AddBeforeId(&'static str, ArcTypedComponent<C>),
Prepend(ArcTypedComponent<C>),
RemoveById(&'static str),
ReplaceById(&'static str, ArcTypedComponent<C>),
Reset,
}
#[derive(Clone, Default)]
pub struct TypedComponents<C: ComponentTrait>(Vec<ArcTypedComponent<C>>);
impl<C: ComponentTrait + Default> TypedComponents<C> {
pub fn new(arc: ArcTypedComponent<C>) -> Self {
TypedComponents::default().with_value(ArcTypedOp::Add(arc))
}
// TypedComponents BUILDER.
#[fn_with]
pub fn alter_value(&mut self, op: ArcTypedOp<C>) -> &mut Self {
match op {
ArcTypedOp::Add(one) => self.0.push(one),
ArcTypedOp::AddAfterId(id, one) => match self.0.iter().position(|c| c.id() == id) {
Some(index) => self.0.insert(index + 1, one),
_ => self.0.push(one),
},
ArcTypedOp::AddBeforeId(id, one) => match self.0.iter().position(|c| c.id() == id) {
Some(index) => self.0.insert(index, one),
_ => self.0.insert(0, one),
},
ArcTypedOp::Prepend(one) => self.0.insert(0, one),
ArcTypedOp::RemoveById(id) => {
if let Some(index) = self.0.iter().position(|c| c.id() == id) {
self.0.remove(index);
}
}
ArcTypedOp::ReplaceById(id, one) => {
for c in self.0.iter_mut() {
if c.id() == id {
*c = one;
break;
}
}
}
ArcTypedOp::Reset => self.0.clear(),
}
self
}
// TypedComponents GETTERS.
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn get_by_id(&self, id: impl Into<String>) -> Option<&ArcTypedComponent<C>> {
let id = id.into();
self.0.iter().find(|&c| c.id() == id)
}
pub fn iter_by_id(&self, id: impl Into<String>) -> impl Iterator<Item = &ArcTypedComponent<C>> {
let id = id.into();
self.0.iter().filter(move |&c| c.id() == id)
}
pub fn iter_by_handle(&self, handle: Handle) -> impl Iterator<Item = &ArcTypedComponent<C>> {
self.0.iter().filter(move |&c| c.handle() == handle)
}
// TypedComponents RENDER.
pub fn render(&self, cx: &mut Context) -> Markup {
let mut components = self.0.clone();
components.sort_by_key(|c| c.weight());
html! {
@for c in components.iter() {
" " (c.render(cx)) " "
}
}
}
}

View file

@ -0,0 +1,60 @@
use crate::html::{ClassesOp, OptionClasses};
pub trait ImplementClassesOp {
fn with_classes(self, op: ClassesOp, classes: impl Into<String>) -> Self;
fn add_classes(&mut self, classes: impl Into<String>) -> &mut Self;
fn prepend_classes(&mut self, classes: impl Into<String>) -> &mut Self;
fn remove_classes(&mut self, classes: impl Into<String>) -> &mut Self;
fn replace_classes(&mut self, rep: impl Into<String>, classes: impl Into<String>) -> &mut Self;
fn toggle_classes(&mut self, classes: impl Into<String>) -> &mut Self;
fn set_classes(&mut self, classes: impl Into<String>) -> &mut Self;
}
pub trait ImplementClasses: ImplementClassesOp {
fn alter_classes(&mut self, op: ClassesOp, classes: impl Into<String>) -> &mut Self;
fn classes(&self) -> &OptionClasses;
}
impl<C: ImplementClasses> ImplementClassesOp for C {
fn with_classes(mut self, op: ClassesOp, classes: impl Into<String>) -> Self {
self.alter_classes(op, classes);
self
}
fn add_classes(&mut self, classes: impl Into<String>) -> &mut Self {
self.alter_classes(ClassesOp::Add, classes);
self
}
fn prepend_classes(&mut self, classes: impl Into<String>) -> &mut Self {
self.alter_classes(ClassesOp::Prepend, classes);
self
}
fn remove_classes(&mut self, classes: impl Into<String>) -> &mut Self {
self.alter_classes(ClassesOp::Remove, classes);
self
}
fn replace_classes(&mut self, rep: impl Into<String>, classes: impl Into<String>) -> &mut Self {
self.alter_classes(ClassesOp::Replace(rep.into()), classes);
self
}
fn toggle_classes(&mut self, classes: impl Into<String>) -> &mut Self {
self.alter_classes(ClassesOp::Toggle, classes);
self
}
fn set_classes(&mut self, classes: impl Into<String>) -> &mut Self {
self.alter_classes(ClassesOp::Set, classes);
self
}
}

View file

@ -0,0 +1,153 @@
use crate::base::component::add_base_assets;
use crate::core::theme::all::{theme_by_single_name, THEME};
use crate::core::theme::ThemeRef;
use crate::html::{html, Assets, HeadScript, HeadStyles, JavaScript, Markup, StyleSheet};
use crate::locale::{LanguageIdentifier, LANGID};
use crate::service::HttpRequest;
use crate::{concat_string, util};
use std::collections::HashMap;
use std::str::FromStr;
pub enum ContextOp {
LangId(&'static LanguageIdentifier),
Theme(&'static str),
// Stylesheets.
AddStyleSheet(StyleSheet),
RemoveStyleSheet(&'static str),
// Styles in head.
AddHeadStyles(HeadStyles),
RemoveHeadStyles(&'static str),
// JavaScripts.
AddJavaScript(JavaScript),
RemoveJavaScript(&'static str),
// Scripts in head.
AddHeadScript(HeadScript),
RemoveHeadScript(&'static str),
// Add assets to properly use base components.
AddBaseAssets,
}
#[rustfmt::skip]
pub struct Context {
request : HttpRequest,
langid : &'static LanguageIdentifier,
theme : ThemeRef,
stylesheet: Assets<StyleSheet>, // Stylesheets.
headstyles: Assets<HeadStyles>, // Styles in head.
javascript: Assets<JavaScript>, // JavaScripts.
headscript: Assets<HeadScript>, // Scripts in head.
params : HashMap<&'static str, String>,
id_counter: usize,
}
impl Context {
#[rustfmt::skip]
pub(crate) fn new(request: HttpRequest) -> Self {
Context {
request,
langid : &LANGID,
theme : *THEME,
stylesheet: Assets::<StyleSheet>::new(), // Stylesheets.
headstyles: Assets::<HeadStyles>::new(), // Styles in head.
javascript: Assets::<JavaScript>::new(), // JavaScripts.
headscript: Assets::<HeadScript>::new(), // Scripts in head.
params : HashMap::<&str, String>::new(),
id_counter: 0,
}
}
#[rustfmt::skip]
pub fn alter(&mut self, op: ContextOp) -> &mut Self {
match op {
ContextOp::LangId(langid) => {
self.langid = langid;
}
ContextOp::Theme(theme_name) => {
self.theme = theme_by_single_name(theme_name).unwrap_or(*THEME);
}
// Stylesheets.
ContextOp::AddStyleSheet(css) => { self.stylesheet.add(css); }
ContextOp::RemoveStyleSheet(path) => { self.stylesheet.remove(path); }
// Styles in head.
ContextOp::AddHeadStyles(styles) => { self.headstyles.add(styles); }
ContextOp::RemoveHeadStyles(path) => { self.headstyles.remove(path); }
// JavaScripts.
ContextOp::AddJavaScript(js) => { self.javascript.add(js); }
ContextOp::RemoveJavaScript(path) => { self.javascript.remove(path); }
// Scripts in head.
ContextOp::AddHeadScript(script) => { self.headscript.add(script); }
ContextOp::RemoveHeadScript(path) => { self.headscript.remove(path); }
// Add assets to properly use base components.
ContextOp::AddBaseAssets => { add_base_assets(self); }
}
self
}
pub fn set_param<T: FromStr + ToString>(&mut self, key: &'static str, value: T) -> &mut Self {
self.params.insert(key, value.to_string());
self
}
pub fn remove_param(&mut self, key: &'static str) -> &mut Self {
self.params.remove(key);
self
}
/// Context GETTERS.
pub fn request(&self) -> &HttpRequest {
&self.request
}
pub fn langid(&self) -> &LanguageIdentifier {
self.langid
}
pub fn theme(&self) -> ThemeRef {
self.theme
}
pub fn get_param<T: FromStr + ToString>(&mut self, key: &'static str) -> Option<T> {
if let Some(value) = self.params.get(key) {
if let Ok(value) = T::from_str(value) {
return Some(value);
}
}
None
}
/// Context PREPARE.
pub fn prepare(&mut self) -> Markup {
html! {
(self.stylesheet.prepare()) // Stylesheets.
(self.headstyles.prepare()) // Styles in head.
(self.javascript.prepare()) // JavaScripts.
(self.headscript.prepare()) // Scripts in head.
}
}
// Context EXTRAS.
pub fn required_id<T>(&mut self, id: Option<String>) -> String {
match id {
Some(id) => id,
None => {
let prefix = util::single_type_name::<T>()
.trim()
.replace(' ', "_")
.to_lowercase();
let prefix = if prefix.is_empty() {
"prefix".to_owned()
} else {
prefix
};
self.id_counter += 1;
concat_string!(prefix, "-", self.id_counter.to_string())
}
}
}
}

View file

@ -0,0 +1,106 @@
use crate::base::action;
use crate::core::component::Context;
use crate::html::{html, Markup, PrepareMarkup};
use crate::{util, ImplementHandle, Weight};
use std::any::Any;
pub trait ComponentBase: Any {
fn render(&mut self, cx: &mut Context) -> Markup;
fn as_ref_any(&self) -> &dyn Any;
fn as_mut_any(&mut self) -> &mut dyn Any;
}
pub trait ComponentTrait: ComponentBase + ImplementHandle + Send + Sync {
fn new() -> Self
where
Self: Sized;
fn name(&self) -> String {
util::single_type_name::<Self>().to_owned()
}
fn description(&self) -> Option<String> {
None
}
fn id(&self) -> Option<String> {
None
}
fn weight(&self) -> Weight {
0
}
#[allow(unused_variables)]
fn is_renderable(&self, cx: &Context) -> bool {
true
}
#[allow(unused_variables)]
fn setup_before_prepare(&mut self, cx: &mut Context) {}
#[allow(unused_variables)]
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
PrepareMarkup::None
}
}
impl<C: ComponentTrait> ComponentBase for C {
fn render(&mut self, cx: &mut Context) -> Markup {
if self.is_renderable(cx) {
// Comprueba el componente antes de prepararlo.
self.setup_before_prepare(cx);
// Acciones del tema antes de preparar el componente.
cx.theme().before_prepare_component(self, cx);
// Acciones de los módulos antes de preparar el componente.
action::component::BeforePrepareComponent::dispatch(self, cx, None);
if let Some(id) = self.id() {
action::component::BeforePrepareComponent::dispatch(self, cx, Some(id));
}
// Renderiza el componente.
let markup = match cx.theme().render_component(self, cx) {
Some(html) => html,
None => match self.prepare_component(cx) {
PrepareMarkup::None => html! {},
PrepareMarkup::Text(text) => html! { (text) },
PrepareMarkup::With(html) => html,
},
};
// Acciones del tema después de preparar el componente.
cx.theme().after_prepare_component(self, cx);
// Acciones de los módulos después de preparar el componente.
action::component::AfterPrepareComponent::dispatch(self, cx, None);
if let Some(id) = self.id() {
action::component::AfterPrepareComponent::dispatch(self, cx, Some(id));
}
markup
} else {
html! {}
}
}
fn as_ref_any(&self) -> &dyn Any {
self
}
fn as_mut_any(&mut self) -> &mut dyn Any {
self
}
}
pub fn component_as_ref<C: ComponentTrait>(component: &dyn ComponentTrait) -> Option<&C> {
component.as_ref_any().downcast_ref::<C>()
}
pub fn component_as_mut<C: ComponentTrait>(component: &mut dyn ComponentTrait) -> Option<&mut C> {
component.as_mut_any().downcast_mut::<C>()
}

View file

@ -0,0 +1,14 @@
use crate::core::component::Context;
use crate::SmartDefault;
pub type FnIsRenderable = fn(cx: &Context) -> bool;
#[derive(SmartDefault)]
pub struct Renderable {
#[default(_code = "render_always")]
pub check: FnIsRenderable,
}
fn render_always(_cx: &Context) -> bool {
true
}

4
src/core/package.rs Normal file
View file

@ -0,0 +1,4 @@
mod definition;
pub use definition::{PackageBase, PackageRef, PackageTrait};
pub(crate) mod all;

162
src/core/package/all.rs Normal file
View file

@ -0,0 +1,162 @@
use crate::core::action::add_action;
use crate::core::package::PackageRef;
use crate::core::theme::all::THEMES;
use crate::{config, service, service_for_static_files, static_files, trace, LazyStatic};
#[cfg(feature = "database")]
use crate::db::*;
use std::sync::RwLock;
static_files!(base);
// PACKAGES ****************************************************************************************
static ENABLED_PACKAGES: LazyStatic<RwLock<Vec<PackageRef>>> =
LazyStatic::new(|| RwLock::new(Vec::new()));
static DROPPED_PACKAGES: LazyStatic<RwLock<Vec<PackageRef>>> =
LazyStatic::new(|| RwLock::new(Vec::new()));
// REGISTER PACKAGES *******************************************************************************
pub fn register_packages(app: PackageRef) {
// List of packages to drop.
let mut list: Vec<PackageRef> = Vec::new();
add_to_dropped(&mut list, app);
DROPPED_PACKAGES.write().unwrap().append(&mut list);
// List of packages to enable.
let mut list: Vec<PackageRef> = Vec::new();
// Enable default themes.
add_to_enabled(&mut list, &crate::base::theme::Basic);
add_to_enabled(&mut list, &crate::base::theme::Chassis);
add_to_enabled(&mut list, &crate::base::theme::Inception);
// Enable application packages.
add_to_enabled(&mut list, app);
list.reverse();
ENABLED_PACKAGES.write().unwrap().append(&mut list);
}
fn add_to_dropped(list: &mut Vec<PackageRef>, package: PackageRef) {
for d in package.drop_packages().iter() {
if !list.iter().any(|p| p.handle() == d.handle()) {
list.push(*d);
trace::debug!("Package \"{}\" dropped", d.single_name());
}
}
for d in package.dependencies().iter() {
add_to_dropped(list, *d);
}
}
fn add_to_enabled(list: &mut Vec<PackageRef>, package: PackageRef) {
if !list.iter().any(|p| p.handle() == package.handle()) {
if DROPPED_PACKAGES
.read()
.unwrap()
.iter()
.any(|p| p.handle() == package.handle())
{
panic!(
"Trying to enable \"{}\" package which is dropped",
package.single_name()
);
} else {
list.push(package);
let mut dependencies = package.dependencies();
dependencies.reverse();
for d in dependencies.iter() {
add_to_enabled(list, *d);
}
if let Some(theme) = package.theme() {
let mut registered_themes = THEMES.write().unwrap();
if !registered_themes
.iter()
.any(|t| t.handle() == theme.handle())
{
registered_themes.push(theme);
trace::debug!("Enabling \"{}\" theme", theme.single_name());
}
} else {
trace::debug!("Enabling \"{}\" package", package.single_name());
}
}
}
}
// REGISTER ACTIONS ********************************************************************************
pub fn register_actions() {
for m in ENABLED_PACKAGES.read().unwrap().iter() {
for a in m.actions().into_iter() {
add_action(a);
}
}
}
// INIT PACKAGES ***********************************************************************************
pub fn init_packages() {
trace::info!("Calling application bootstrap");
for m in ENABLED_PACKAGES.read().unwrap().iter() {
m.init();
}
}
// RUN MIGRATIONS **********************************************************************************
#[cfg(feature = "database")]
pub fn run_migrations() {
if let Some(dbconn) = &*DBCONN {
if let Err(e) = run_now({
struct Migrator;
impl MigratorTrait for Migrator {
fn migrations() -> Vec<MigrationItem> {
let mut migrations = vec![];
for m in ENABLED_PACKAGES.read().unwrap().iter() {
migrations.append(&mut m.migrations());
}
migrations
}
}
Migrator::up(SchemaManagerConnection::Connection(dbconn), None)
}) {
trace::error!("Database upgrade failed ({})", e);
};
if let Err(e) = run_now({
struct Migrator;
impl MigratorTrait for Migrator {
fn migrations() -> Vec<MigrationItem> {
let mut migrations = vec![];
for m in DROPPED_PACKAGES.read().unwrap().iter() {
migrations.append(&mut m.migrations());
}
migrations
}
}
Migrator::down(SchemaManagerConnection::Connection(dbconn), None)
}) {
trace::error!("Database downgrade failed ({})", e);
};
}
}
// CONFIGURE SERVICES ******************************************************************************
pub fn configure_services(scfg: &mut service::web::ServiceConfig) {
service_for_static_files!(
scfg,
base => "/base",
[&config::SETTINGS.dev.pagetop_project_dir, "static/base"]
);
for m in ENABLED_PACKAGES.read().unwrap().iter() {
m.configure_service(scfg);
}
}

View file

@ -0,0 +1,57 @@
use crate::core::action::Action;
use crate::core::theme::ThemeRef;
use crate::locale::L10n;
use crate::{actions, service, util, ImplementHandle};
#[cfg(feature = "database")]
use crate::{db::MigrationItem, migrations};
pub type PackageRef = &'static dyn PackageTrait;
pub trait PackageBase {
fn single_name(&self) -> &'static str;
}
/// Los paquetes deben implementar este *trait*.
pub trait PackageTrait: ImplementHandle + PackageBase + Send + Sync {
fn name(&self) -> L10n {
L10n::n(self.single_name())
}
fn description(&self) -> L10n {
L10n::none()
}
fn theme(&self) -> Option<ThemeRef> {
None
}
fn dependencies(&self) -> Vec<PackageRef> {
vec![]
}
fn drop_packages(&self) -> Vec<PackageRef> {
vec![]
}
fn actions(&self) -> Vec<Action> {
actions![]
}
fn init(&self) {}
#[cfg(feature = "database")]
#[allow(unused_variables)]
fn migrations(&self) -> Vec<MigrationItem> {
migrations![]
}
#[allow(unused_variables)]
fn configure_service(&self, scfg: &mut service::web::ServiceConfig) {}
}
impl<M: ?Sized + PackageTrait> PackageBase for M {
fn single_name(&self) -> &'static str {
util::single_type_name::<Self>()
}
}

8
src/core/theme.rs Normal file
View file

@ -0,0 +1,8 @@
mod definition;
pub use definition::{ThemeRef, ThemeTrait};
mod regions;
pub(crate) use regions::ComponentsInRegions;
pub use regions::{add_component_in, Region};
pub(crate) mod all;

32
src/core/theme/all.rs Normal file
View file

@ -0,0 +1,32 @@
use crate::config;
use crate::core::theme::ThemeRef;
use crate::LazyStatic;
use std::sync::RwLock;
// THEMES ******************************************************************************************
pub static THEMES: LazyStatic<RwLock<Vec<ThemeRef>>> = LazyStatic::new(|| RwLock::new(Vec::new()));
// DEFAULT THEME ***********************************************************************************
pub static THEME: LazyStatic<ThemeRef> =
LazyStatic::new(|| match theme_by_single_name(&config::SETTINGS.app.theme) {
Some(theme) => theme,
None => &crate::base::theme::Inception,
});
// THEME BY NAME ***********************************************************************************
pub fn theme_by_single_name(single_name: &str) -> Option<ThemeRef> {
let single_name = single_name.to_lowercase();
match THEMES
.read()
.unwrap()
.iter()
.find(|t| t.single_name().to_lowercase() == single_name)
{
Some(theme) => Some(*theme),
_ => None,
}
}

View file

@ -0,0 +1,174 @@
use crate::core::component::{ComponentTrait, Context};
use crate::core::package::PackageTrait;
use crate::html::{html, Favicon, Markup, OptionId};
use crate::locale::L10n;
use crate::response::page::Page;
use crate::{concat_string, config};
pub type ThemeRef = &'static dyn ThemeTrait;
/// Los temas deben implementar este "trait".
pub trait ThemeTrait: PackageTrait + Send + Sync {
#[rustfmt::skip]
fn regions(&self) -> Vec<(&'static str, L10n)> {
vec![
("header", L10n::l("header")),
("pagetop", L10n::l("pagetop")),
("content", L10n::l("content")),
("sidebar", L10n::l("sidebar")),
("footer", L10n::l("footer")),
]
}
fn prepare_region(&self, page: &mut Page, region: &str) -> Markup {
let render_region = page.components_in(region).render(page.context());
if render_region.is_empty() {
html! {}
} else {
let id = OptionId::new(region).get().unwrap();
let id_inner = concat_string!(id, "__inner");
html! {
div id=(id) class="pt-region" {
div id=(id_inner) class="pt-region__inner" {
(render_region)
}
}
}
}
}
#[allow(unused_variables)]
fn before_prepare_body(&self, page: &mut Page) {}
fn prepare_body(&self, page: &mut Page) -> Markup {
let skip_to = concat_string!("#", page.skip_to().get().unwrap_or("content".to_owned()));
html! {
body class=[page.body_classes().get()] {
@if let Some(skip) = L10n::l("skip_to_content").using(page.context().langid()) {
div class="pt-body__skip" {
a href=(skip_to) { (skip) }
}
}
div class="pt-body__wrapper" {
div class="pt-body__regions" {
(self.prepare_region(page, "header"))
(self.prepare_region(page, "pagetop"))
div class="pt-content" {
div class="pt-content__wrapper" {
(self.prepare_region(page, "content"))
(self.prepare_region(page, "sidebar"))
}
}
(self.prepare_region(page, "footer"))
}
}
}
}
}
fn after_prepare_body(&self, page: &mut Page) {
if page.favicon().is_none() {
page.alter_favicon(Some(Favicon::new().with_icon("/base/favicon.ico")));
}
}
fn prepare_head(&self, page: &mut Page) -> Markup {
let viewport = "width=device-width, initial-scale=1, shrink-to-fit=no";
html! {
head {
meta charset="utf-8";
@if let Some(title) = page.title() {
title { (config::SETTINGS.app.name) (" - ") (title) }
} @else {
title { (config::SETTINGS.app.name) }
}
@if let Some(description) = page.description() {
meta name="description" content=(description);
}
meta name="viewport" content=(viewport);
@for (name, content) in page.metadata() {
meta name=(name) content=(content) {}
}
meta http-equiv="X-UA-Compatible" content="IE=edge";
@for (property, content) in page.properties() {
meta property=(property) content=(content) {}
}
@if let Some(favicon) = page.favicon() {
(favicon.prepare())
}
(page.context().prepare())
}
}
}
#[rustfmt::skip]
#[allow(unused_variables)]
fn before_prepare_component(
&self,
component: &mut dyn ComponentTrait,
cx: &mut Context,
) {
/*
Cómo usarlo:
match component.handle() {
BLOCK_COMPONENT => {
let block = component_as_mut::<Block>(component);
block.alter_title("New title");
},
_ => {},
}
*/
}
#[rustfmt::skip]
#[allow(unused_variables)]
fn after_prepare_component(
&self,
component: &mut dyn ComponentTrait,
cx: &mut Context,
) {
/*
Cómo usarlo:
match component.handle() {
BLOCK_COMPONENT => {
let block = component_as_mut::<Block>(component);
block.alter_title("New title");
},
_ => {},
}
*/
}
#[rustfmt::skip]
#[allow(unused_variables)]
fn render_component(
&self,
component: &dyn ComponentTrait,
cx: &mut Context,
) -> Option<Markup> {
None
/*
Cómo usarlo:
match component.handle() {
BLOCK_COMPONENT => {
let block = component_as_ref::<Block>(component);
match block.template() {
"default" => Some(block_default(block)),
_ => None,
}
},
_ => None,
}
*/
}
}

61
src/core/theme/regions.rs Normal file
View file

@ -0,0 +1,61 @@
use crate::core::component::{AnyComponents, ArcAnyComponent, ArcAnyOp};
use crate::core::theme::ThemeRef;
use crate::{Handle, LazyStatic, SmartDefault};
use std::collections::HashMap;
use std::sync::RwLock;
static THEME_REGIONS: LazyStatic<RwLock<HashMap<Handle, ComponentsInRegions>>> =
LazyStatic::new(|| RwLock::new(HashMap::new()));
static COMMON_REGIONS: LazyStatic<RwLock<ComponentsInRegions>> =
LazyStatic::new(|| RwLock::new(ComponentsInRegions::default()));
#[derive(SmartDefault)]
pub struct ComponentsInRegions(HashMap<&'static str, AnyComponents>);
impl ComponentsInRegions {
pub fn new(region: &'static str, arc: ArcAnyComponent) -> Self {
let mut regions = ComponentsInRegions::default();
regions.add_component_in(region, arc);
regions
}
pub fn add_component_in(&mut self, region: &'static str, arc: ArcAnyComponent) {
if let Some(region) = self.0.get_mut(region) {
region.alter_value(ArcAnyOp::Add(arc));
} else {
self.0.insert(region, AnyComponents::new(arc));
}
}
pub fn get_components(&self, theme: ThemeRef, region: &str) -> AnyComponents {
let common = COMMON_REGIONS.read().unwrap();
if let Some(r) = THEME_REGIONS.read().unwrap().get(&theme.handle()) {
AnyComponents::merge(&[common.0.get(region), self.0.get(region), r.0.get(region)])
} else {
AnyComponents::merge(&[common.0.get(region), self.0.get(region)])
}
}
}
pub enum Region {
Named(&'static str),
OfTheme(ThemeRef, &'static str),
}
pub fn add_component_in(region: Region, arc: ArcAnyComponent) {
match region {
Region::Named(name) => {
COMMON_REGIONS.write().unwrap().add_component_in(name, arc);
}
Region::OfTheme(theme, region) => {
let mut regions = THEME_REGIONS.write().unwrap();
if let Some(r) = regions.get_mut(&theme.handle()) {
r.add_component_in(region, arc);
} else {
regions.insert(theme.handle(), ComponentsInRegions::new(region, arc));
}
}
}
}