🎉 Start refactoring PageTop for Sycamore
This commit is contained in:
parent
9f62955acb
commit
38ca8f1d1c
155 changed files with 2805 additions and 10960 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -1,6 +1,4 @@
|
|||
**/target
|
||||
**/log/*.log*
|
||||
**/local.*.toml
|
||||
**/local.toml
|
||||
Cargo.lock
|
||||
**/*.local.*.toml
|
||||
workdir
|
||||
|
|
|
|||
2515
Cargo.lock
generated
Normal file
2515
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
125
Cargo.toml
125
Cargo.toml
|
|
@ -1,62 +1,85 @@
|
|||
[package]
|
||||
name = "pagetop"
|
||||
version = "0.0.56"
|
||||
edition = "2021"
|
||||
|
||||
description = "An opinionated web framework to build modular Server-Side Rendering web solutions."
|
||||
homepage = "https://pagetop.cillero.es"
|
||||
repository = "https://github.com/manuelcillero/pagetop"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
authors = [
|
||||
"Manuel Cillero <manuel@cillero.es>"
|
||||
]
|
||||
categories = [
|
||||
"web-programming", "gui", "development-tools", "asynchronous"
|
||||
]
|
||||
keywords = [
|
||||
"pagetop", "web", "framework", "frontend", "ssr"
|
||||
]
|
||||
exclude = [
|
||||
"examples/", "helpers/", "tests/"
|
||||
]
|
||||
rust-version = "1.80.0"
|
||||
|
||||
[workspace]
|
||||
members = ["helpers/*"]
|
||||
resolver = "2"
|
||||
members = [
|
||||
# Helpers
|
||||
"helpers/pagetop-build",
|
||||
"helpers/pagetop-macros",
|
||||
|
||||
[lib]
|
||||
name = "pagetop"
|
||||
# Packages
|
||||
"packages/pagetop",
|
||||
# "packages/pagetop-bootsier",
|
||||
# "packages/pagetop-macros",
|
||||
|
||||
[dependencies]
|
||||
chrono = "0.4.38"
|
||||
concat-string = "1.0.1"
|
||||
figlet-rs = "0.1.5"
|
||||
itoa = "1.0.11"
|
||||
nom = "7.1.3"
|
||||
paste = "1.0.15"
|
||||
substring = "1.4.5"
|
||||
terminal_size = "0.3.0"
|
||||
toml = "0.8.19"
|
||||
# App
|
||||
"packages/drust",
|
||||
|
||||
tracing = "0.1.40"
|
||||
tracing-appender = "0.2.3"
|
||||
tracing-subscriber = { version = "0.3.18", features = ["json", "env-filter"] }
|
||||
tracing-actix-web = "0.7.11"
|
||||
# Examples
|
||||
# "examples/app-basic",
|
||||
# "examples/hello-world",
|
||||
# "examples/hello-name",
|
||||
]
|
||||
|
||||
fluent-templates = "0.9.4"
|
||||
unic-langid = { version = "0.9.5", features = ["macros"] }
|
||||
[workspace.package]
|
||||
homepage = "https://pagetop.cillero.es"
|
||||
repository = "https://github.com/manuelcillero/pagetop"
|
||||
license = "MIT OR Apache-2.0"
|
||||
authors = ["Manuel Cillero <manuel@cillero.es>"]
|
||||
|
||||
actix-web = "4"
|
||||
actix-session = { version = "0.10.0", features = ["cookie-session"] }
|
||||
|
||||
actix-web-files = { package = "actix-files", version = "0.6.6" }
|
||||
actix-web-static-files = "4.0.1"
|
||||
[workspace.dependencies]
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
static-files = "0.2.4"
|
||||
|
||||
pagetop-build = { version = "0.0", path = "helpers/pagetop-build" }
|
||||
pagetop-macros = { version = "0.0", path = "helpers/pagetop-macros" }
|
||||
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
pagetop = { version = "0.0", path = "packages/pagetop" }
|
||||
|
||||
[build-dependencies]
|
||||
pagetop-build = { version = "0.0", path = "helpers/pagetop-build" }
|
||||
|
||||
|
||||
|
||||
actix-web = "4"
|
||||
actix-web-files = { package = "actix-files", version = "0.6" }
|
||||
actix-web-static-files = "4.0"
|
||||
actix-session = { version = "0.10", features = ["cookie-session"] }
|
||||
fluent-templates = "0.11"
|
||||
nom = "7.1"
|
||||
substring = "1.4"
|
||||
tracing = "0.1"
|
||||
tracing-appender = "0.2"
|
||||
tracing-subscriber = { version = "0.3", features = ["json", "env-filter"] }
|
||||
tracing-actix-web = "0.7"
|
||||
unic-langid = { version = "0.9", features = ["macros"] }
|
||||
|
||||
|
||||
#[dependencies]
|
||||
#chrono = "0.4.38"
|
||||
#concat-string = "1.0.1"
|
||||
#figlet-rs = "0.1.5"
|
||||
#itoa = "1.0.11"
|
||||
#nom = "7.1.3"
|
||||
#paste = "1.0.15"
|
||||
#substring = "1.4.5"
|
||||
#terminal_size = "0.4.0"
|
||||
#toml = "0.8.19"
|
||||
|
||||
#tracing = "0.1.40"
|
||||
#tracing-appender = "0.2.3"
|
||||
#tracing-subscriber = { version = "0.3.18", features = ["json", "env-filter"] }
|
||||
#tracing-actix-web = "0.7.14"
|
||||
|
||||
#fluent-templates = "0.11.0"
|
||||
#unic-langid = { version = "0.9.5", features = ["macros"] }
|
||||
|
||||
#actix-web = "4"
|
||||
#actix-session = { version = "0.10.1", features = ["cookie-session"] }
|
||||
|
||||
#actix-web-files = { package = "actix-files", version = "0.6.6" }
|
||||
#actix-web-static-files = "4.0.1"
|
||||
#static-files = "0.2.4"
|
||||
|
||||
#pagetop-macros = { version = "0.0", path = "helpers/pagetop-macros" }
|
||||
|
||||
#serde = { version = "1.0", features = ["derive"] }
|
||||
|
||||
#[build-dependencies]
|
||||
#pagetop-build = { version = "0.0", path = "helpers/pagetop-build" }
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ accessible via `http://localhost:8088` under default settings.
|
|||
# 📂 Helpers
|
||||
|
||||
* [pagetop-macros](https://github.com/manuelcillero/pagetop/tree/latest/helpers/pagetop-macros):
|
||||
A collection of procedural macros that enhance the development experience within PageTop.
|
||||
A collection of macros that enhance the development experience within PageTop.
|
||||
|
||||
* [pagetop-build](https://github.com/manuelcillero/pagetop/tree/latest/helpers/pagetop-build):
|
||||
Simplifies the process of embedding resources directly into binary files for PageTop applications.
|
||||
|
|
|
|||
|
|
@ -4,19 +4,14 @@ version = "0.0.11"
|
|||
edition = "2021"
|
||||
|
||||
description = "Simplifies the process of embedding resources in PageTop app binaries."
|
||||
homepage = "https://pagetop.cillero.es"
|
||||
repository = "https://github.com/manuelcillero/pagetop"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
authors = [
|
||||
"Manuel Cillero <manuel@cillero.es>"
|
||||
]
|
||||
categories = [
|
||||
"development-tools::build-utils", "web-programming"
|
||||
]
|
||||
keywords = [
|
||||
"pagetop", "build", "assets", "resources", "static"
|
||||
]
|
||||
categories = ["development-tools::build-utils", "web-programming"]
|
||||
keywords = ["pagetop", "build", "assets", "resources", "static"]
|
||||
|
||||
homepage = { workspace = true }
|
||||
repository = { workspace = true }
|
||||
license = { workspace = true }
|
||||
authors = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
static-files = "0.2.4"
|
||||
static-files.workspace = true
|
||||
|
|
|
|||
|
|
@ -3,28 +3,20 @@ name = "pagetop-macros"
|
|||
version = "0.0.13"
|
||||
edition = "2021"
|
||||
|
||||
description = "A collection of procedural macros that boost PageTop development."
|
||||
homepage = "https://pagetop.cillero.es"
|
||||
repository = "https://github.com/manuelcillero/pagetop"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "A collection of macros that boost PageTop development."
|
||||
|
||||
authors = [
|
||||
"Manuel Cillero <manuel@cillero.es>"
|
||||
]
|
||||
categories = [
|
||||
"development-tools::procedural-macro-helpers", "web-programming"
|
||||
]
|
||||
keywords = [
|
||||
"pagetop", "macros", "proc-macros", "codegen"
|
||||
]
|
||||
categories = ["development-tools::procedural-macro-helpers", "web-programming"]
|
||||
keywords = ["pagetop", "macros", "proc-macros", "codegen"]
|
||||
|
||||
homepage = { workspace = true }
|
||||
repository = { workspace = true }
|
||||
license = { workspace = true }
|
||||
authors = { workspace = true }
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
concat-string = "1.0.1"
|
||||
proc-macro2 = "1.0"
|
||||
proc-macro-crate = "3.1.0"
|
||||
proc-macro-error = "1.0.4"
|
||||
quote = "1.0"
|
||||
syn = { version = "2.0", features = ["full"] }
|
||||
proc-macro2 = "1.0.89"
|
||||
quote = "1.0.37"
|
||||
syn = { version = "2.0.87", features = ["full"] }
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<h1>PageTop Macros</h1>
|
||||
|
||||
<p>A collection of procedural macros that boost PageTop development.</p>
|
||||
<p>A collection of macros that boost PageTop development.</p>
|
||||
|
||||
[](#-license)
|
||||
[](https://docs.rs/pagetop-macros)
|
||||
|
|
@ -25,11 +25,10 @@ frequent changes. Production use is not recommended until version **0.1.0**.
|
|||
|
||||
# 🔖 Credits
|
||||
|
||||
This crate includes an adapted version of [maud-macros](https://crates.io/crates/maud_macros),
|
||||
version [0.25.0](https://github.com/lambda-fairy/maud/tree/v0.25.0/maud_macros), by
|
||||
[Chris Wong](https://crates.io/users/lambda-fairy). This adaptation integrates its functionalities
|
||||
into **PageTop**, eliminating the need for direct references to `maud` in the `Cargo.toml` file of
|
||||
each project.
|
||||
This crate includes an adapted version of [SmartDefault](https://crates.io/crates/smart_default)
|
||||
(version 0.7.1) by [Jane Doe](https://crates.io/users/jane-doe), named `AutoDefault`, to streamline
|
||||
Default implementations in **PageTop** projects and eliminate the need to explicitly add
|
||||
`smart_default` to `Cargo.toml` files.
|
||||
|
||||
|
||||
# 📜 License
|
||||
|
|
|
|||
|
|
@ -1,118 +1,16 @@
|
|||
mod maud;
|
||||
mod smart_default;
|
||||
|
||||
use concat_string::concat_string;
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro_error::proc_macro_error;
|
||||
use quote::{quote, quote_spanned, ToTokens};
|
||||
use syn::{parse_macro_input, parse_str, DeriveInput, ItemFn};
|
||||
use quote::quote;
|
||||
use syn::{parse_macro_input, DeriveInput};
|
||||
|
||||
#[proc_macro]
|
||||
#[proc_macro_error]
|
||||
pub fn html(input: TokenStream) -> TokenStream {
|
||||
maud::expand(input.into()).into()
|
||||
}
|
||||
|
||||
/// Macro attribute to generate builder methods from `set_` methods.
|
||||
///
|
||||
/// This macro takes a method with the `set_` prefix and generates a corresponding method with the
|
||||
/// `with_` prefix to use in the builder pattern.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic if a parameter identifier is not found in the argument list.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// #[fn_builder]
|
||||
/// pub fn set_example(&mut self) -> &mut Self {
|
||||
/// // implementation
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Will generate:
|
||||
///
|
||||
/// ```
|
||||
/// pub fn with_example(mut self) -> Self {
|
||||
/// self.set_example();
|
||||
/// self
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn fn_builder(_: TokenStream, item: TokenStream) -> TokenStream {
|
||||
let fn_set = parse_macro_input!(item as ItemFn);
|
||||
let fn_set_name = fn_set.sig.ident.to_string();
|
||||
|
||||
if !fn_set_name.starts_with("set_") {
|
||||
let expanded = quote_spanned! {
|
||||
fn_set.sig.ident.span() =>
|
||||
compile_error!("expected a \"pub fn set_...() -> &mut Self\" method");
|
||||
};
|
||||
return expanded.into();
|
||||
#[proc_macro_derive(AutoDefault, attributes(default))]
|
||||
pub fn derive_auto_default(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
match smart_default::body_impl::impl_my_derive(&input) {
|
||||
Ok(output) => output.into(),
|
||||
Err(error) => error.to_compile_error().into(),
|
||||
}
|
||||
|
||||
let fn_with_name = fn_set_name.replace("set_", "with_");
|
||||
let fn_with_generics = if fn_set.sig.generics.params.is_empty() {
|
||||
fn_with_name.clone()
|
||||
} else {
|
||||
let g = &fn_set.sig.generics;
|
||||
concat_string!(fn_with_name, quote! { #g }.to_string())
|
||||
};
|
||||
|
||||
let where_clause = fn_set
|
||||
.sig
|
||||
.generics
|
||||
.where_clause
|
||||
.as_ref()
|
||||
.map_or(String::new(), |where_clause| {
|
||||
concat_string!(quote! { #where_clause }.to_string(), " ")
|
||||
});
|
||||
|
||||
let args: Vec<String> = fn_set
|
||||
.sig
|
||||
.inputs
|
||||
.iter()
|
||||
.skip(1)
|
||||
.map(|arg| arg.to_token_stream().to_string())
|
||||
.collect();
|
||||
|
||||
let params: Vec<String> = args
|
||||
.iter()
|
||||
.map(|arg| {
|
||||
arg.split_whitespace()
|
||||
.next()
|
||||
.unwrap()
|
||||
.trim_end_matches(':')
|
||||
.to_string()
|
||||
})
|
||||
.collect();
|
||||
|
||||
#[rustfmt::skip]
|
||||
let fn_with = parse_str::<ItemFn>(concat_string!("
|
||||
pub fn ", fn_with_generics, "(mut self, ", args.join(", "), ") -> Self ", where_clause, "{
|
||||
self.", fn_set_name, "(", params.join(", "), ");
|
||||
self
|
||||
}
|
||||
").as_str()).unwrap();
|
||||
|
||||
#[rustfmt::skip]
|
||||
let fn_set_doc = concat_string!(
|
||||
"<p id=\"method.", fn_with_name, "\" style=\"margin-bottom: 12px;\">Use ",
|
||||
"<code class=\"code-header\">pub fn <span class=\"fn\" href=\"#method.", fn_with_name, "\">",
|
||||
fn_with_name,
|
||||
"</span>(self, …) -> Self</code> for the <a href=\"#method.new\">builder pattern</a>.",
|
||||
"</p>"
|
||||
);
|
||||
|
||||
let expanded = quote! {
|
||||
#[doc(hidden)]
|
||||
#fn_with
|
||||
#[inline]
|
||||
#[doc = #fn_set_doc]
|
||||
#fn_set
|
||||
};
|
||||
expanded.into()
|
||||
}
|
||||
|
||||
/// Marks async main function as the `PageTop` entry-point.
|
||||
|
|
@ -154,43 +52,3 @@ pub fn test(_: TokenStream, item: TokenStream) -> TokenStream {
|
|||
output.extend(item);
|
||||
output
|
||||
}
|
||||
|
||||
#[proc_macro_derive(AutoDefault, attributes(default))]
|
||||
pub fn derive_auto_default(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
match smart_default::body_impl::impl_my_derive(&input) {
|
||||
Ok(output) => output.into(),
|
||||
Err(error) => error.to_compile_error().into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro_derive(ComponentClasses)]
|
||||
pub fn derive_component_classes(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
let name = &input.ident;
|
||||
|
||||
let fn_set_doc = concat_string!(
|
||||
"<p id=\"method.with_classes\">",
|
||||
"Use <code class=\"code-header\">",
|
||||
" <span class=\"fn\" href=\"#method.with_classes\">with_classes</span>(self, …) -> Self ",
|
||||
"</code> to apply the <a href=\"#method.new\">builder pattern</a>.",
|
||||
"</p>"
|
||||
);
|
||||
|
||||
let expanded = quote! {
|
||||
impl ComponentClasses for #name {
|
||||
#[inline]
|
||||
#[doc = #fn_set_doc]
|
||||
fn set_classes(&mut self, op: ClassesOp, classes: impl Into<String>) -> &mut Self {
|
||||
self.classes.set_value(op, classes);
|
||||
self
|
||||
}
|
||||
|
||||
fn classes(&self) -> &OptionClasses {
|
||||
&self.classes
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TokenStream::from(expanded)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,39 +0,0 @@
|
|||
// #![doc(html_root_url = "https://docs.rs/maud_macros/0.25.0")]
|
||||
// TokenStream values are reference counted, and the mental overhead of tracking
|
||||
// lifetimes outweighs the marginal gains from explicit borrowing
|
||||
// #![allow(clippy::needless_pass_by_value)]
|
||||
|
||||
mod ast;
|
||||
mod escape;
|
||||
mod generate;
|
||||
mod parse;
|
||||
|
||||
use proc_macro2::{Ident, Span, TokenStream, TokenTree};
|
||||
use proc_macro_crate::{crate_name, FoundCrate};
|
||||
use quote::quote;
|
||||
|
||||
pub fn expand(input: TokenStream) -> TokenStream {
|
||||
let output_ident = TokenTree::Ident(Ident::new("__maud_output", Span::mixed_site()));
|
||||
// Heuristic: the size of the resulting markup tends to correlate with the
|
||||
// code size of the template itself
|
||||
let size_hint = input.to_string().len();
|
||||
let markups = parse::parse(input);
|
||||
let stmts = generate::generate(markups, output_ident.clone());
|
||||
|
||||
let found_crate = crate_name("pagetop").expect("pagetop is present in `Cargo.toml`");
|
||||
let pre_escaped = match found_crate {
|
||||
FoundCrate::Itself => quote!(
|
||||
crate::html::PreEscaped(#output_ident)
|
||||
),
|
||||
_ => quote!(
|
||||
pagetop::html::PreEscaped(#output_ident)
|
||||
),
|
||||
};
|
||||
|
||||
quote!({
|
||||
extern crate alloc;
|
||||
let mut #output_ident = alloc::string::String::with_capacity(#size_hint);
|
||||
#stmts
|
||||
#pre_escaped
|
||||
})
|
||||
}
|
||||
|
|
@ -1,221 +0,0 @@
|
|||
use proc_macro2::{TokenStream, TokenTree};
|
||||
use proc_macro_error::SpanRange;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Markup {
|
||||
/// Used as a placeholder value on parse error.
|
||||
ParseError {
|
||||
span: SpanRange,
|
||||
},
|
||||
Block(Block),
|
||||
Literal {
|
||||
content: String,
|
||||
span: SpanRange,
|
||||
},
|
||||
Symbol {
|
||||
symbol: TokenStream,
|
||||
},
|
||||
Splice {
|
||||
expr: TokenStream,
|
||||
outer_span: SpanRange,
|
||||
},
|
||||
Element {
|
||||
name: TokenStream,
|
||||
attrs: Vec<Attr>,
|
||||
body: ElementBody,
|
||||
},
|
||||
Let {
|
||||
at_span: SpanRange,
|
||||
tokens: TokenStream,
|
||||
},
|
||||
Special {
|
||||
segments: Vec<Special>,
|
||||
},
|
||||
Match {
|
||||
at_span: SpanRange,
|
||||
head: TokenStream,
|
||||
arms: Vec<MatchArm>,
|
||||
arms_span: SpanRange,
|
||||
},
|
||||
}
|
||||
|
||||
impl Markup {
|
||||
pub fn span(&self) -> SpanRange {
|
||||
match *self {
|
||||
Markup::ParseError { span } => span,
|
||||
Markup::Block(ref block) => block.span(),
|
||||
Markup::Literal { span, .. } => span,
|
||||
Markup::Symbol { ref symbol } => span_tokens(symbol.clone()),
|
||||
Markup::Splice { outer_span, .. } => outer_span,
|
||||
Markup::Element {
|
||||
ref name, ref body, ..
|
||||
} => {
|
||||
let name_span = span_tokens(name.clone());
|
||||
name_span.join_range(body.span())
|
||||
}
|
||||
Markup::Let {
|
||||
at_span,
|
||||
ref tokens,
|
||||
} => at_span.join_range(span_tokens(tokens.clone())),
|
||||
Markup::Special { ref segments } => join_ranges(segments.iter().map(Special::span)),
|
||||
Markup::Match {
|
||||
at_span, arms_span, ..
|
||||
} => at_span.join_range(arms_span),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Attr {
|
||||
Class {
|
||||
dot_span: SpanRange,
|
||||
name: Markup,
|
||||
toggler: Option<Toggler>,
|
||||
},
|
||||
Id {
|
||||
hash_span: SpanRange,
|
||||
name: Markup,
|
||||
},
|
||||
Named {
|
||||
named_attr: NamedAttr,
|
||||
},
|
||||
}
|
||||
|
||||
impl Attr {
|
||||
pub fn span(&self) -> SpanRange {
|
||||
match *self {
|
||||
Attr::Class {
|
||||
dot_span,
|
||||
ref name,
|
||||
ref toggler,
|
||||
} => {
|
||||
let name_span = name.span();
|
||||
let dot_name_span = dot_span.join_range(name_span);
|
||||
if let Some(toggler) = toggler {
|
||||
dot_name_span.join_range(toggler.cond_span)
|
||||
} else {
|
||||
dot_name_span
|
||||
}
|
||||
}
|
||||
Attr::Id {
|
||||
hash_span,
|
||||
ref name,
|
||||
} => {
|
||||
let name_span = name.span();
|
||||
hash_span.join_range(name_span)
|
||||
}
|
||||
Attr::Named { ref named_attr } => named_attr.span(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ElementBody {
|
||||
Void { semi_span: SpanRange },
|
||||
Block { block: Block },
|
||||
}
|
||||
|
||||
impl ElementBody {
|
||||
pub fn span(&self) -> SpanRange {
|
||||
match *self {
|
||||
ElementBody::Void { semi_span } => semi_span,
|
||||
ElementBody::Block { ref block } => block.span(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Block {
|
||||
pub markups: Vec<Markup>,
|
||||
pub outer_span: SpanRange,
|
||||
}
|
||||
|
||||
impl Block {
|
||||
pub fn span(&self) -> SpanRange {
|
||||
self.outer_span
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Special {
|
||||
pub at_span: SpanRange,
|
||||
pub head: TokenStream,
|
||||
pub body: Block,
|
||||
}
|
||||
|
||||
impl Special {
|
||||
pub fn span(&self) -> SpanRange {
|
||||
let body_span = self.body.span();
|
||||
self.at_span.join_range(body_span)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NamedAttr {
|
||||
pub name: TokenStream,
|
||||
pub attr_type: AttrType,
|
||||
}
|
||||
|
||||
impl NamedAttr {
|
||||
fn span(&self) -> SpanRange {
|
||||
let name_span = span_tokens(self.name.clone());
|
||||
if let Some(attr_type_span) = self.attr_type.span() {
|
||||
name_span.join_range(attr_type_span)
|
||||
} else {
|
||||
name_span
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AttrType {
|
||||
Normal { value: Markup },
|
||||
Optional { toggler: Toggler },
|
||||
Empty { toggler: Option<Toggler> },
|
||||
}
|
||||
|
||||
impl AttrType {
|
||||
fn span(&self) -> Option<SpanRange> {
|
||||
match *self {
|
||||
AttrType::Normal { ref value } => Some(value.span()),
|
||||
AttrType::Optional { ref toggler } => Some(toggler.span()),
|
||||
AttrType::Empty { ref toggler } => toggler.as_ref().map(Toggler::span),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Toggler {
|
||||
pub cond: TokenStream,
|
||||
pub cond_span: SpanRange,
|
||||
}
|
||||
|
||||
impl Toggler {
|
||||
fn span(&self) -> SpanRange {
|
||||
self.cond_span
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MatchArm {
|
||||
pub head: TokenStream,
|
||||
pub body: Block,
|
||||
}
|
||||
|
||||
pub fn span_tokens<I: IntoIterator<Item = TokenTree>>(tokens: I) -> SpanRange {
|
||||
join_ranges(tokens.into_iter().map(|s| SpanRange::single_span(s.span())))
|
||||
}
|
||||
|
||||
pub fn join_ranges<I: IntoIterator<Item = SpanRange>>(ranges: I) -> SpanRange {
|
||||
let mut iter = ranges.into_iter();
|
||||
let first = match iter.next() {
|
||||
Some(span) => span,
|
||||
None => return SpanRange::call_site(),
|
||||
};
|
||||
let last = iter.last().unwrap_or(first);
|
||||
first.join_range(last)
|
||||
}
|
||||
|
||||
pub fn name_to_string(name: TokenStream) -> String {
|
||||
name.into_iter().map(|token| token.to_string()).collect()
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
// !!!!!!!! PLEASE KEEP THIS IN SYNC WITH `maud/src/escape.rs` !!!!!!!!!
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use alloc::string::String;
|
||||
|
||||
pub fn escape_to_string(input: &str, output: &mut String) {
|
||||
for b in input.bytes() {
|
||||
match b {
|
||||
b'&' => output.push_str("&"),
|
||||
b'<' => output.push_str("<"),
|
||||
b'>' => output.push_str(">"),
|
||||
b'"' => output.push_str("""),
|
||||
_ => unsafe { output.as_mut_vec().push(b) },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
extern crate alloc;
|
||||
|
||||
use super::escape_to_string;
|
||||
use alloc::string::String;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let mut s = String::new();
|
||||
escape_to_string("<script>launchMissiles()</script>", &mut s);
|
||||
assert_eq!(s, "<script>launchMissiles()</script>");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,308 +0,0 @@
|
|||
use proc_macro2::{Delimiter, Group, Ident, Literal, Span, TokenStream, TokenTree};
|
||||
use proc_macro_error::SpanRange;
|
||||
use quote::quote;
|
||||
|
||||
use crate::maud::{ast::*, escape};
|
||||
|
||||
use proc_macro_crate::{crate_name, FoundCrate};
|
||||
|
||||
pub fn generate(markups: Vec<Markup>, output_ident: TokenTree) -> TokenStream {
|
||||
let mut build = Builder::new(output_ident.clone());
|
||||
Generator::new(output_ident).markups(markups, &mut build);
|
||||
build.finish()
|
||||
}
|
||||
|
||||
struct Generator {
|
||||
output_ident: TokenTree,
|
||||
}
|
||||
|
||||
impl Generator {
|
||||
fn new(output_ident: TokenTree) -> Generator {
|
||||
Generator { output_ident }
|
||||
}
|
||||
|
||||
fn builder(&self) -> Builder {
|
||||
Builder::new(self.output_ident.clone())
|
||||
}
|
||||
|
||||
fn markups(&self, markups: Vec<Markup>, build: &mut Builder) {
|
||||
for markup in markups {
|
||||
self.markup(markup, build);
|
||||
}
|
||||
}
|
||||
|
||||
fn markup(&self, markup: Markup, build: &mut Builder) {
|
||||
match markup {
|
||||
Markup::ParseError { .. } => {}
|
||||
Markup::Block(Block {
|
||||
markups,
|
||||
outer_span,
|
||||
}) => {
|
||||
if markups
|
||||
.iter()
|
||||
.any(|markup| matches!(*markup, Markup::Let { .. }))
|
||||
{
|
||||
self.block(
|
||||
Block {
|
||||
markups,
|
||||
outer_span,
|
||||
},
|
||||
build,
|
||||
);
|
||||
} else {
|
||||
self.markups(markups, build);
|
||||
}
|
||||
}
|
||||
Markup::Literal { content, .. } => build.push_escaped(&content),
|
||||
Markup::Symbol { symbol } => self.name(symbol, build),
|
||||
Markup::Splice { expr, .. } => self.splice(expr, build),
|
||||
Markup::Element { name, attrs, body } => self.element(name, attrs, body, build),
|
||||
Markup::Let { tokens, .. } => build.push_tokens(tokens),
|
||||
Markup::Special { segments } => {
|
||||
for Special { head, body, .. } in segments {
|
||||
build.push_tokens(head);
|
||||
self.block(body, build);
|
||||
}
|
||||
}
|
||||
Markup::Match {
|
||||
head,
|
||||
arms,
|
||||
arms_span,
|
||||
..
|
||||
} => {
|
||||
let body = {
|
||||
let mut build = self.builder();
|
||||
for MatchArm { head, body } in arms {
|
||||
build.push_tokens(head);
|
||||
self.block(body, &mut build);
|
||||
}
|
||||
build.finish()
|
||||
};
|
||||
let mut body = TokenTree::Group(Group::new(Delimiter::Brace, body));
|
||||
body.set_span(arms_span.collapse());
|
||||
build.push_tokens(quote!(#head #body));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn block(
|
||||
&self,
|
||||
Block {
|
||||
markups,
|
||||
outer_span,
|
||||
}: Block,
|
||||
build: &mut Builder,
|
||||
) {
|
||||
let block = {
|
||||
let mut build = self.builder();
|
||||
self.markups(markups, &mut build);
|
||||
build.finish()
|
||||
};
|
||||
let mut block = TokenTree::Group(Group::new(Delimiter::Brace, block));
|
||||
block.set_span(outer_span.collapse());
|
||||
build.push_tokens(TokenStream::from(block));
|
||||
}
|
||||
|
||||
fn splice(&self, expr: TokenStream, build: &mut Builder) {
|
||||
let output_ident = self.output_ident.clone();
|
||||
|
||||
let found_crate = crate_name("pagetop").expect("pagetop is present in `Cargo.toml`");
|
||||
build.push_tokens(match found_crate {
|
||||
FoundCrate::Itself => quote!(
|
||||
crate::html::html_private::render_to!(&#expr, &mut #output_ident);
|
||||
),
|
||||
_ => quote!(
|
||||
pagetop::html::html_private::render_to!(&#expr, &mut #output_ident);
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
fn element(&self, name: TokenStream, attrs: Vec<Attr>, body: ElementBody, build: &mut Builder) {
|
||||
build.push_str("<");
|
||||
self.name(name.clone(), build);
|
||||
self.attrs(attrs, build);
|
||||
build.push_str(">");
|
||||
if let ElementBody::Block { block } = body {
|
||||
self.markups(block.markups, build);
|
||||
build.push_str("</");
|
||||
self.name(name, build);
|
||||
build.push_str(">");
|
||||
}
|
||||
}
|
||||
|
||||
fn name(&self, name: TokenStream, build: &mut Builder) {
|
||||
build.push_escaped(&name_to_string(name));
|
||||
}
|
||||
|
||||
fn attrs(&self, attrs: Vec<Attr>, build: &mut Builder) {
|
||||
for NamedAttr { name, attr_type } in desugar_attrs(attrs) {
|
||||
match attr_type {
|
||||
AttrType::Normal { value } => {
|
||||
build.push_str(" ");
|
||||
self.name(name, build);
|
||||
build.push_str("=\"");
|
||||
self.markup(value, build);
|
||||
build.push_str("\"");
|
||||
}
|
||||
AttrType::Optional {
|
||||
toggler: Toggler { cond, .. },
|
||||
} => {
|
||||
let inner_value = quote!(inner_value);
|
||||
let body = {
|
||||
let mut build = self.builder();
|
||||
build.push_str(" ");
|
||||
self.name(name, &mut build);
|
||||
build.push_str("=\"");
|
||||
self.splice(inner_value.clone(), &mut build);
|
||||
build.push_str("\"");
|
||||
build.finish()
|
||||
};
|
||||
build.push_tokens(quote!(if let Some(#inner_value) = (#cond) { #body }));
|
||||
}
|
||||
AttrType::Empty { toggler: None } => {
|
||||
build.push_str(" ");
|
||||
self.name(name, build);
|
||||
}
|
||||
AttrType::Empty {
|
||||
toggler: Some(Toggler { cond, .. }),
|
||||
} => {
|
||||
let body = {
|
||||
let mut build = self.builder();
|
||||
build.push_str(" ");
|
||||
self.name(name, &mut build);
|
||||
build.finish()
|
||||
};
|
||||
build.push_tokens(quote!(if (#cond) { #body }));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
fn desugar_attrs(attrs: Vec<Attr>) -> Vec<NamedAttr> {
|
||||
let mut classes_static = vec![];
|
||||
let mut classes_toggled = vec![];
|
||||
let mut ids = vec![];
|
||||
let mut named_attrs = vec![];
|
||||
for attr in attrs {
|
||||
match attr {
|
||||
Attr::Class {
|
||||
name,
|
||||
toggler: Some(toggler),
|
||||
..
|
||||
} => classes_toggled.push((name, toggler)),
|
||||
Attr::Class {
|
||||
name,
|
||||
toggler: None,
|
||||
..
|
||||
} => classes_static.push(name),
|
||||
Attr::Id { name, .. } => ids.push(name),
|
||||
Attr::Named { named_attr } => named_attrs.push(named_attr),
|
||||
}
|
||||
}
|
||||
let classes = desugar_classes_or_ids("class", classes_static, classes_toggled);
|
||||
let ids = desugar_classes_or_ids("id", ids, vec![]);
|
||||
classes.into_iter().chain(ids).chain(named_attrs).collect()
|
||||
}
|
||||
|
||||
fn desugar_classes_or_ids(
|
||||
attr_name: &'static str,
|
||||
values_static: Vec<Markup>,
|
||||
values_toggled: Vec<(Markup, Toggler)>,
|
||||
) -> Option<NamedAttr> {
|
||||
if values_static.is_empty() && values_toggled.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let mut markups = Vec::new();
|
||||
let mut leading_space = false;
|
||||
for name in values_static {
|
||||
markups.extend(prepend_leading_space(name, &mut leading_space));
|
||||
}
|
||||
for (name, Toggler { cond, cond_span }) in values_toggled {
|
||||
let body = Block {
|
||||
markups: prepend_leading_space(name, &mut leading_space),
|
||||
// TODO: is this correct?
|
||||
outer_span: cond_span,
|
||||
};
|
||||
markups.push(Markup::Special {
|
||||
segments: vec![Special {
|
||||
at_span: SpanRange::call_site(),
|
||||
head: quote!(if (#cond)),
|
||||
body,
|
||||
}],
|
||||
});
|
||||
}
|
||||
Some(NamedAttr {
|
||||
name: TokenStream::from(TokenTree::Ident(Ident::new(attr_name, Span::call_site()))),
|
||||
attr_type: AttrType::Normal {
|
||||
value: Markup::Block(Block {
|
||||
markups,
|
||||
outer_span: SpanRange::call_site(),
|
||||
}),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn prepend_leading_space(name: Markup, leading_space: &mut bool) -> Vec<Markup> {
|
||||
let mut markups = Vec::new();
|
||||
if *leading_space {
|
||||
markups.push(Markup::Literal {
|
||||
content: " ".to_owned(),
|
||||
span: name.span(),
|
||||
});
|
||||
}
|
||||
*leading_space = true;
|
||||
markups.push(name);
|
||||
markups
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
struct Builder {
|
||||
output_ident: TokenTree,
|
||||
tokens: Vec<TokenTree>,
|
||||
tail: String,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
fn new(output_ident: TokenTree) -> Builder {
|
||||
Builder {
|
||||
output_ident,
|
||||
tokens: Vec::new(),
|
||||
tail: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn push_str(&mut self, string: &str) {
|
||||
self.tail.push_str(string);
|
||||
}
|
||||
|
||||
fn push_escaped(&mut self, string: &str) {
|
||||
escape::escape_to_string(string, &mut self.tail);
|
||||
}
|
||||
|
||||
fn push_tokens(&mut self, tokens: TokenStream) {
|
||||
self.cut();
|
||||
self.tokens.extend(tokens);
|
||||
}
|
||||
|
||||
fn cut(&mut self) {
|
||||
if self.tail.is_empty() {
|
||||
return;
|
||||
}
|
||||
let push_str_expr = {
|
||||
let output_ident = self.output_ident.clone();
|
||||
let string = TokenTree::Literal(Literal::string(&self.tail));
|
||||
quote!(#output_ident.push_str(#string);)
|
||||
};
|
||||
self.tail.clear();
|
||||
self.tokens.extend(push_str_expr);
|
||||
}
|
||||
|
||||
fn finish(mut self) -> TokenStream {
|
||||
self.cut();
|
||||
self.tokens.into_iter().collect()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,752 +0,0 @@
|
|||
use proc_macro2::{Delimiter, Ident, Literal, Spacing, Span, TokenStream, TokenTree};
|
||||
use proc_macro_error::{abort, abort_call_site, emit_error, SpanRange};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use syn::Lit;
|
||||
|
||||
use crate::maud::ast;
|
||||
|
||||
pub fn parse(input: TokenStream) -> Vec<ast::Markup> {
|
||||
Parser::new(input).markups()
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Parser {
|
||||
/// If we're inside an attribute, then this contains the attribute name.
|
||||
current_attr: Option<String>,
|
||||
input: <TokenStream as IntoIterator>::IntoIter,
|
||||
}
|
||||
|
||||
impl Iterator for Parser {
|
||||
type Item = TokenTree;
|
||||
|
||||
fn next(&mut self) -> Option<TokenTree> {
|
||||
self.input.next()
|
||||
}
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
fn new(input: TokenStream) -> Parser {
|
||||
Parser {
|
||||
current_attr: None,
|
||||
input: input.into_iter(),
|
||||
}
|
||||
}
|
||||
|
||||
fn with_input(&self, input: TokenStream) -> Parser {
|
||||
Parser {
|
||||
current_attr: self.current_attr.clone(),
|
||||
input: input.into_iter(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the next token in the stream without consuming it.
|
||||
fn peek(&mut self) -> Option<TokenTree> {
|
||||
self.clone().next()
|
||||
}
|
||||
|
||||
/// Returns the next two tokens in the stream without consuming them.
|
||||
fn peek2(&mut self) -> Option<(TokenTree, Option<TokenTree>)> {
|
||||
let mut clone = self.clone();
|
||||
clone.next().map(|first| (first, clone.next()))
|
||||
}
|
||||
|
||||
/// Advances the cursor by one step.
|
||||
fn advance(&mut self) {
|
||||
self.next();
|
||||
}
|
||||
|
||||
/// Advances the cursor by two steps.
|
||||
fn advance2(&mut self) {
|
||||
self.next();
|
||||
self.next();
|
||||
}
|
||||
|
||||
/// Parses multiple blocks of markup.
|
||||
fn markups(&mut self) -> Vec<ast::Markup> {
|
||||
let mut result = Vec::new();
|
||||
loop {
|
||||
match self.peek2() {
|
||||
None => break,
|
||||
Some((TokenTree::Punct(ref punct), _)) if punct.as_char() == ';' => self.advance(),
|
||||
Some((TokenTree::Punct(ref punct), Some(TokenTree::Ident(ref ident))))
|
||||
if punct.as_char() == '@' && *ident == "let" =>
|
||||
{
|
||||
self.advance2();
|
||||
let keyword = TokenTree::Ident(ident.clone());
|
||||
result.push(self.let_expr(punct.span(), keyword));
|
||||
}
|
||||
_ => result.push(self.markup()),
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Parses a single block of markup.
|
||||
fn markup(&mut self) -> ast::Markup {
|
||||
let token = match self.peek() {
|
||||
Some(token) => token,
|
||||
None => {
|
||||
abort_call_site!("unexpected end of input");
|
||||
}
|
||||
};
|
||||
let markup = match token {
|
||||
// Literal
|
||||
TokenTree::Literal(literal) => {
|
||||
self.advance();
|
||||
self.literal(literal)
|
||||
}
|
||||
// Special form
|
||||
TokenTree::Punct(ref punct) if punct.as_char() == '@' => {
|
||||
self.advance();
|
||||
let at_span = punct.span();
|
||||
match self.next() {
|
||||
Some(TokenTree::Ident(ident)) => {
|
||||
let keyword = TokenTree::Ident(ident.clone());
|
||||
match ident.to_string().as_str() {
|
||||
"if" => {
|
||||
let mut segments = Vec::new();
|
||||
self.if_expr(at_span, vec![keyword], &mut segments);
|
||||
ast::Markup::Special { segments }
|
||||
}
|
||||
"while" => self.while_expr(at_span, keyword),
|
||||
"for" => self.for_expr(at_span, keyword),
|
||||
"match" => self.match_expr(at_span, keyword),
|
||||
"let" => {
|
||||
let span = SpanRange {
|
||||
first: at_span,
|
||||
last: ident.span(),
|
||||
};
|
||||
abort!(span, "`@let` only works inside a block");
|
||||
}
|
||||
other => {
|
||||
let span = SpanRange {
|
||||
first: at_span,
|
||||
last: ident.span(),
|
||||
};
|
||||
abort!(span, "unknown keyword `@{}`", other);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
abort!(at_span, "expected keyword after `@`");
|
||||
}
|
||||
}
|
||||
}
|
||||
// Element
|
||||
TokenTree::Ident(ident) => {
|
||||
let ident_string = ident.to_string();
|
||||
match ident_string.as_str() {
|
||||
"if" | "while" | "for" | "match" | "let" => {
|
||||
abort!(
|
||||
ident,
|
||||
"found keyword `{}`", ident_string;
|
||||
help = "should this be a `@{}`?", ident_string
|
||||
);
|
||||
}
|
||||
"true" | "false" => {
|
||||
if let Some(attr_name) = &self.current_attr {
|
||||
emit_error!(
|
||||
ident,
|
||||
r#"attribute value must be a string"#;
|
||||
help = "to declare an empty attribute, omit the equals sign: `{}`",
|
||||
attr_name;
|
||||
help = "to toggle the attribute, use square brackets: `{}[some_boolean_flag]`",
|
||||
attr_name;
|
||||
);
|
||||
return ast::Markup::ParseError {
|
||||
span: SpanRange::single_span(ident.span()),
|
||||
};
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// `.try_namespaced_name()` should never fail as we've
|
||||
// already seen an `Ident`
|
||||
let name = self.try_namespaced_name().expect("identifier");
|
||||
self.element(name)
|
||||
}
|
||||
// Div element shorthand
|
||||
TokenTree::Punct(ref punct) if punct.as_char() == '.' || punct.as_char() == '#' => {
|
||||
let name = TokenTree::Ident(Ident::new("div", punct.span()));
|
||||
self.element(name.into())
|
||||
}
|
||||
// Splice
|
||||
TokenTree::Group(ref group) if group.delimiter() == Delimiter::Parenthesis => {
|
||||
self.advance();
|
||||
ast::Markup::Splice {
|
||||
expr: group.stream(),
|
||||
outer_span: SpanRange::single_span(group.span()),
|
||||
}
|
||||
}
|
||||
// Block
|
||||
TokenTree::Group(ref group) if group.delimiter() == Delimiter::Brace => {
|
||||
self.advance();
|
||||
ast::Markup::Block(self.block(group.stream(), SpanRange::single_span(group.span())))
|
||||
}
|
||||
// ???
|
||||
token => {
|
||||
abort!(token, "invalid syntax");
|
||||
}
|
||||
};
|
||||
markup
|
||||
}
|
||||
|
||||
/// Parses a literal string.
|
||||
fn literal(&mut self, literal: Literal) -> ast::Markup {
|
||||
match Lit::new(literal.clone()) {
|
||||
Lit::Str(lit_str) => {
|
||||
return ast::Markup::Literal {
|
||||
content: lit_str.value(),
|
||||
span: SpanRange::single_span(literal.span()),
|
||||
}
|
||||
}
|
||||
// Boolean literals are idents, so `Lit::Bool` is handled in
|
||||
// `markup`, not here.
|
||||
Lit::Int(..) | Lit::Float(..) => {
|
||||
emit_error!(literal, r#"literal must be double-quoted: `"{}"`"#, literal);
|
||||
}
|
||||
Lit::Char(lit_char) => {
|
||||
emit_error!(
|
||||
literal,
|
||||
r#"literal must be double-quoted: `"{}"`"#,
|
||||
lit_char.value(),
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
emit_error!(literal, "expected string");
|
||||
}
|
||||
}
|
||||
ast::Markup::ParseError {
|
||||
span: SpanRange::single_span(literal.span()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses an `@if` expression.
|
||||
///
|
||||
/// The leading `@if` should already be consumed.
|
||||
fn if_expr(&mut self, at_span: Span, prefix: Vec<TokenTree>, segments: &mut Vec<ast::Special>) {
|
||||
let mut head = prefix;
|
||||
let body = loop {
|
||||
match self.next() {
|
||||
Some(TokenTree::Group(ref block)) if block.delimiter() == Delimiter::Brace => {
|
||||
break self.block(block.stream(), SpanRange::single_span(block.span()));
|
||||
}
|
||||
Some(token) => head.push(token),
|
||||
None => {
|
||||
let mut span = ast::span_tokens(head);
|
||||
span.first = at_span;
|
||||
abort!(span, "expected body for this `@if`");
|
||||
}
|
||||
}
|
||||
};
|
||||
segments.push(ast::Special {
|
||||
at_span: SpanRange::single_span(at_span),
|
||||
head: head.into_iter().collect(),
|
||||
body,
|
||||
});
|
||||
self.else_if_expr(segments)
|
||||
}
|
||||
|
||||
/// Parses an optional `@else if` or `@else`.
|
||||
///
|
||||
/// The leading `@else if` or `@else` should *not* already be consumed.
|
||||
fn else_if_expr(&mut self, segments: &mut Vec<ast::Special>) {
|
||||
match self.peek2() {
|
||||
Some((TokenTree::Punct(ref punct), Some(TokenTree::Ident(ref else_keyword))))
|
||||
if punct.as_char() == '@' && *else_keyword == "else" =>
|
||||
{
|
||||
self.advance2();
|
||||
let at_span = punct.span();
|
||||
let else_keyword = TokenTree::Ident(else_keyword.clone());
|
||||
match self.peek() {
|
||||
// `@else if`
|
||||
Some(TokenTree::Ident(ref if_keyword)) if *if_keyword == "if" => {
|
||||
self.advance();
|
||||
let if_keyword = TokenTree::Ident(if_keyword.clone());
|
||||
self.if_expr(at_span, vec![else_keyword, if_keyword], segments)
|
||||
}
|
||||
// Just an `@else`
|
||||
_ => match self.next() {
|
||||
Some(TokenTree::Group(ref group))
|
||||
if group.delimiter() == Delimiter::Brace =>
|
||||
{
|
||||
let body =
|
||||
self.block(group.stream(), SpanRange::single_span(group.span()));
|
||||
segments.push(ast::Special {
|
||||
at_span: SpanRange::single_span(at_span),
|
||||
head: vec![else_keyword].into_iter().collect(),
|
||||
body,
|
||||
});
|
||||
}
|
||||
_ => {
|
||||
let span = SpanRange {
|
||||
first: at_span,
|
||||
last: else_keyword.span(),
|
||||
};
|
||||
abort!(span, "expected body for this `@else`");
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
// We didn't find an `@else`; stop
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses an `@while` expression.
|
||||
///
|
||||
/// The leading `@while` should already be consumed.
|
||||
fn while_expr(&mut self, at_span: Span, keyword: TokenTree) -> ast::Markup {
|
||||
let keyword_span = keyword.span();
|
||||
let mut head = vec![keyword];
|
||||
let body = loop {
|
||||
match self.next() {
|
||||
Some(TokenTree::Group(ref block)) if block.delimiter() == Delimiter::Brace => {
|
||||
break self.block(block.stream(), SpanRange::single_span(block.span()));
|
||||
}
|
||||
Some(token) => head.push(token),
|
||||
None => {
|
||||
let span = SpanRange {
|
||||
first: at_span,
|
||||
last: keyword_span,
|
||||
};
|
||||
abort!(span, "expected body for this `@while`");
|
||||
}
|
||||
}
|
||||
};
|
||||
ast::Markup::Special {
|
||||
segments: vec![ast::Special {
|
||||
at_span: SpanRange::single_span(at_span),
|
||||
head: head.into_iter().collect(),
|
||||
body,
|
||||
}],
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses a `@for` expression.
|
||||
///
|
||||
/// The leading `@for` should already be consumed.
|
||||
fn for_expr(&mut self, at_span: Span, keyword: TokenTree) -> ast::Markup {
|
||||
let keyword_span = keyword.span();
|
||||
let mut head = vec![keyword];
|
||||
loop {
|
||||
match self.next() {
|
||||
Some(TokenTree::Ident(ref in_keyword)) if *in_keyword == "in" => {
|
||||
head.push(TokenTree::Ident(in_keyword.clone()));
|
||||
break;
|
||||
}
|
||||
Some(token) => head.push(token),
|
||||
None => {
|
||||
let span = SpanRange {
|
||||
first: at_span,
|
||||
last: keyword_span,
|
||||
};
|
||||
abort!(span, "missing `in` in `@for` loop");
|
||||
}
|
||||
}
|
||||
}
|
||||
let body = loop {
|
||||
match self.next() {
|
||||
Some(TokenTree::Group(ref block)) if block.delimiter() == Delimiter::Brace => {
|
||||
break self.block(block.stream(), SpanRange::single_span(block.span()));
|
||||
}
|
||||
Some(token) => head.push(token),
|
||||
None => {
|
||||
let span = SpanRange {
|
||||
first: at_span,
|
||||
last: keyword_span,
|
||||
};
|
||||
abort!(span, "expected body for this `@for`");
|
||||
}
|
||||
}
|
||||
};
|
||||
ast::Markup::Special {
|
||||
segments: vec![ast::Special {
|
||||
at_span: SpanRange::single_span(at_span),
|
||||
head: head.into_iter().collect(),
|
||||
body,
|
||||
}],
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses a `@match` expression.
|
||||
///
|
||||
/// The leading `@match` should already be consumed.
|
||||
fn match_expr(&mut self, at_span: Span, keyword: TokenTree) -> ast::Markup {
|
||||
let keyword_span = keyword.span();
|
||||
let mut head = vec![keyword];
|
||||
let (arms, arms_span) = loop {
|
||||
match self.next() {
|
||||
Some(TokenTree::Group(ref body)) if body.delimiter() == Delimiter::Brace => {
|
||||
let span = SpanRange::single_span(body.span());
|
||||
break (self.with_input(body.stream()).match_arms(), span);
|
||||
}
|
||||
Some(token) => head.push(token),
|
||||
None => {
|
||||
let span = SpanRange {
|
||||
first: at_span,
|
||||
last: keyword_span,
|
||||
};
|
||||
abort!(span, "expected body for this `@match`");
|
||||
}
|
||||
}
|
||||
};
|
||||
ast::Markup::Match {
|
||||
at_span: SpanRange::single_span(at_span),
|
||||
head: head.into_iter().collect(),
|
||||
arms,
|
||||
arms_span,
|
||||
}
|
||||
}
|
||||
|
||||
fn match_arms(&mut self) -> Vec<ast::MatchArm> {
|
||||
let mut arms = Vec::new();
|
||||
while let Some(arm) = self.match_arm() {
|
||||
arms.push(arm);
|
||||
}
|
||||
arms
|
||||
}
|
||||
|
||||
fn match_arm(&mut self) -> Option<ast::MatchArm> {
|
||||
let mut head = Vec::new();
|
||||
loop {
|
||||
match self.peek2() {
|
||||
Some((TokenTree::Punct(ref eq), Some(TokenTree::Punct(ref gt))))
|
||||
if eq.as_char() == '='
|
||||
&& gt.as_char() == '>'
|
||||
&& eq.spacing() == Spacing::Joint =>
|
||||
{
|
||||
self.advance2();
|
||||
head.push(TokenTree::Punct(eq.clone()));
|
||||
head.push(TokenTree::Punct(gt.clone()));
|
||||
break;
|
||||
}
|
||||
Some((token, _)) => {
|
||||
self.advance();
|
||||
head.push(token);
|
||||
}
|
||||
None => {
|
||||
if head.is_empty() {
|
||||
return None;
|
||||
} else {
|
||||
let head_span = ast::span_tokens(head);
|
||||
abort!(head_span, "unexpected end of @match pattern");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let body = match self.next() {
|
||||
// $pat => { $stmts }
|
||||
Some(TokenTree::Group(ref body)) if body.delimiter() == Delimiter::Brace => {
|
||||
let body = self.block(body.stream(), SpanRange::single_span(body.span()));
|
||||
// Trailing commas are optional if the match arm is a braced block
|
||||
if let Some(TokenTree::Punct(ref punct)) = self.peek() {
|
||||
if punct.as_char() == ',' {
|
||||
self.advance();
|
||||
}
|
||||
}
|
||||
body
|
||||
}
|
||||
// $pat => $expr
|
||||
Some(first_token) => {
|
||||
let mut span = SpanRange::single_span(first_token.span());
|
||||
let mut body = vec![first_token];
|
||||
loop {
|
||||
match self.next() {
|
||||
Some(TokenTree::Punct(ref punct)) if punct.as_char() == ',' => break,
|
||||
Some(token) => {
|
||||
span.last = token.span();
|
||||
body.push(token);
|
||||
}
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
self.block(body.into_iter().collect(), span)
|
||||
}
|
||||
None => {
|
||||
let span = ast::span_tokens(head);
|
||||
abort!(span, "unexpected end of @match arm");
|
||||
}
|
||||
};
|
||||
Some(ast::MatchArm {
|
||||
head: head.into_iter().collect(),
|
||||
body,
|
||||
})
|
||||
}
|
||||
|
||||
/// Parses a `@let` expression.
|
||||
///
|
||||
/// The leading `@let` should already be consumed.
|
||||
fn let_expr(&mut self, at_span: Span, keyword: TokenTree) -> ast::Markup {
|
||||
let mut tokens = vec![keyword];
|
||||
loop {
|
||||
match self.next() {
|
||||
Some(token) => match token {
|
||||
TokenTree::Punct(ref punct) if punct.as_char() == '=' => {
|
||||
tokens.push(token.clone());
|
||||
break;
|
||||
}
|
||||
_ => tokens.push(token),
|
||||
},
|
||||
None => {
|
||||
let mut span = ast::span_tokens(tokens);
|
||||
span.first = at_span;
|
||||
abort!(span, "unexpected end of `@let` expression");
|
||||
}
|
||||
}
|
||||
}
|
||||
loop {
|
||||
match self.next() {
|
||||
Some(token) => match token {
|
||||
TokenTree::Punct(ref punct) if punct.as_char() == ';' => {
|
||||
tokens.push(token.clone());
|
||||
break;
|
||||
}
|
||||
_ => tokens.push(token),
|
||||
},
|
||||
None => {
|
||||
let mut span = ast::span_tokens(tokens);
|
||||
span.first = at_span;
|
||||
abort!(
|
||||
span,
|
||||
"unexpected end of `@let` expression";
|
||||
help = "are you missing a semicolon?"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
ast::Markup::Let {
|
||||
at_span: SpanRange::single_span(at_span),
|
||||
tokens: tokens.into_iter().collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses an element node.
|
||||
///
|
||||
/// The element name should already be consumed.
|
||||
fn element(&mut self, name: TokenStream) -> ast::Markup {
|
||||
if self.current_attr.is_some() {
|
||||
let span = ast::span_tokens(name);
|
||||
abort!(span, "unexpected element");
|
||||
}
|
||||
let attrs = self.attrs();
|
||||
let body = match self.peek() {
|
||||
Some(TokenTree::Punct(ref punct))
|
||||
if punct.as_char() == ';' || punct.as_char() == '/' =>
|
||||
{
|
||||
// Void element
|
||||
self.advance();
|
||||
if punct.as_char() == '/' {
|
||||
emit_error!(
|
||||
punct,
|
||||
"void elements must use `;`, not `/`";
|
||||
help = "change this to `;`";
|
||||
help = "see https://github.com/lambda-fairy/maud/pull/315 for details";
|
||||
);
|
||||
}
|
||||
ast::ElementBody::Void {
|
||||
semi_span: SpanRange::single_span(punct.span()),
|
||||
}
|
||||
}
|
||||
Some(_) => match self.markup() {
|
||||
ast::Markup::Block(block) => ast::ElementBody::Block { block },
|
||||
markup => {
|
||||
let markup_span = markup.span();
|
||||
abort!(
|
||||
markup_span,
|
||||
"element body must be wrapped in braces";
|
||||
help = "see https://github.com/lambda-fairy/maud/pull/137 for details"
|
||||
);
|
||||
}
|
||||
},
|
||||
None => abort_call_site!("expected `;`, found end of macro"),
|
||||
};
|
||||
ast::Markup::Element { name, attrs, body }
|
||||
}
|
||||
|
||||
/// Parses the attributes of an element.
|
||||
fn attrs(&mut self) -> Vec<ast::Attr> {
|
||||
let mut attrs = Vec::new();
|
||||
loop {
|
||||
if let Some(name) = self.try_namespaced_name() {
|
||||
// Attribute
|
||||
match self.peek() {
|
||||
// Non-empty attribute
|
||||
Some(TokenTree::Punct(ref punct)) if punct.as_char() == '=' => {
|
||||
self.advance();
|
||||
// Parse a value under an attribute context
|
||||
assert!(self.current_attr.is_none());
|
||||
self.current_attr = Some(ast::name_to_string(name.clone()));
|
||||
let attr_type = match self.attr_toggler() {
|
||||
Some(toggler) => ast::AttrType::Optional { toggler },
|
||||
None => {
|
||||
let value = self.markup();
|
||||
ast::AttrType::Normal { value }
|
||||
}
|
||||
};
|
||||
self.current_attr = None;
|
||||
attrs.push(ast::Attr::Named {
|
||||
named_attr: ast::NamedAttr { name, attr_type },
|
||||
});
|
||||
}
|
||||
// Empty attribute (legacy syntax)
|
||||
Some(TokenTree::Punct(ref punct)) if punct.as_char() == '?' => {
|
||||
self.advance();
|
||||
let toggler = self.attr_toggler();
|
||||
attrs.push(ast::Attr::Named {
|
||||
named_attr: ast::NamedAttr {
|
||||
name: name.clone(),
|
||||
attr_type: ast::AttrType::Empty { toggler },
|
||||
},
|
||||
});
|
||||
}
|
||||
// Empty attribute (new syntax)
|
||||
_ => {
|
||||
let toggler = self.attr_toggler();
|
||||
attrs.push(ast::Attr::Named {
|
||||
named_attr: ast::NamedAttr {
|
||||
name: name.clone(),
|
||||
attr_type: ast::AttrType::Empty { toggler },
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match self.peek() {
|
||||
// Class shorthand
|
||||
Some(TokenTree::Punct(ref punct)) if punct.as_char() == '.' => {
|
||||
self.advance();
|
||||
let name = self.class_or_id_name();
|
||||
let toggler = self.attr_toggler();
|
||||
attrs.push(ast::Attr::Class {
|
||||
dot_span: SpanRange::single_span(punct.span()),
|
||||
name,
|
||||
toggler,
|
||||
});
|
||||
}
|
||||
// ID shorthand
|
||||
Some(TokenTree::Punct(ref punct)) if punct.as_char() == '#' => {
|
||||
self.advance();
|
||||
let name = self.class_or_id_name();
|
||||
attrs.push(ast::Attr::Id {
|
||||
hash_span: SpanRange::single_span(punct.span()),
|
||||
name,
|
||||
});
|
||||
}
|
||||
// If it's not a valid attribute, backtrack and bail out
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut attr_map: HashMap<String, Vec<SpanRange>> = HashMap::new();
|
||||
let mut has_class = false;
|
||||
for attr in &attrs {
|
||||
let name = match attr {
|
||||
ast::Attr::Class { .. } => {
|
||||
if has_class {
|
||||
// Only check the first class to avoid spurious duplicates
|
||||
continue;
|
||||
}
|
||||
has_class = true;
|
||||
"class".to_string()
|
||||
}
|
||||
ast::Attr::Id { .. } => "id".to_string(),
|
||||
ast::Attr::Named { named_attr } => named_attr
|
||||
.name
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|token| token.to_string())
|
||||
.collect(),
|
||||
};
|
||||
let entry = attr_map.entry(name).or_default();
|
||||
entry.push(attr.span());
|
||||
}
|
||||
|
||||
for (name, spans) in attr_map {
|
||||
if spans.len() > 1 {
|
||||
let mut spans = spans.into_iter();
|
||||
let first_span = spans.next().expect("spans should be non-empty");
|
||||
abort!(first_span, "duplicate attribute `{}`", name);
|
||||
}
|
||||
}
|
||||
|
||||
attrs
|
||||
}
|
||||
|
||||
/// Parses the name of a class or ID.
|
||||
fn class_or_id_name(&mut self) -> ast::Markup {
|
||||
if let Some(symbol) = self.try_name() {
|
||||
ast::Markup::Symbol { symbol }
|
||||
} else {
|
||||
self.markup()
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses the `[cond]` syntax after an empty attribute or class shorthand.
|
||||
fn attr_toggler(&mut self) -> Option<ast::Toggler> {
|
||||
match self.peek() {
|
||||
Some(TokenTree::Group(ref group)) if group.delimiter() == Delimiter::Bracket => {
|
||||
self.advance();
|
||||
Some(ast::Toggler {
|
||||
cond: group.stream(),
|
||||
cond_span: SpanRange::single_span(group.span()),
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses an identifier, without dealing with namespaces.
|
||||
fn try_name(&mut self) -> Option<TokenStream> {
|
||||
let mut result = Vec::new();
|
||||
if let Some(token @ TokenTree::Ident(_)) = self.peek() {
|
||||
self.advance();
|
||||
result.push(token);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
let mut expect_ident = false;
|
||||
loop {
|
||||
expect_ident = match self.peek() {
|
||||
Some(TokenTree::Punct(ref punct)) if punct.as_char() == '-' => {
|
||||
self.advance();
|
||||
result.push(TokenTree::Punct(punct.clone()));
|
||||
true
|
||||
}
|
||||
Some(TokenTree::Ident(ref ident)) if expect_ident => {
|
||||
self.advance();
|
||||
result.push(TokenTree::Ident(ident.clone()));
|
||||
false
|
||||
}
|
||||
_ => break,
|
||||
};
|
||||
}
|
||||
Some(result.into_iter().collect())
|
||||
}
|
||||
|
||||
/// Parses a HTML element or attribute name, along with a namespace
|
||||
/// if necessary.
|
||||
fn try_namespaced_name(&mut self) -> Option<TokenStream> {
|
||||
let mut result = vec![self.try_name()?];
|
||||
if let Some(TokenTree::Punct(ref punct)) = self.peek() {
|
||||
if punct.as_char() == ':' {
|
||||
self.advance();
|
||||
result.push(TokenStream::from(TokenTree::Punct(punct.clone())));
|
||||
result.push(self.try_name()?);
|
||||
}
|
||||
}
|
||||
Some(result.into_iter().collect())
|
||||
}
|
||||
|
||||
/// Parses the given token stream as a Maud expression.
|
||||
fn block(&mut self, body: TokenStream, outer_span: SpanRange) -> ast::Block {
|
||||
let markups = self.with_input(body).markups();
|
||||
ast::Block {
|
||||
markups,
|
||||
outer_span,
|
||||
}
|
||||
}
|
||||
}
|
||||
39
packages/drust/Cargo.toml
Normal file
39
packages/drust/Cargo.toml
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
[package]
|
||||
name = "drust"
|
||||
version = "0.0.3"
|
||||
edition = "2021"
|
||||
|
||||
description = "A modern web Content Management System to share your world."
|
||||
|
||||
homepage = { workspace = true }
|
||||
repository = { workspace = true }
|
||||
license = { workspace = true }
|
||||
authors = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
pagetop.workspace = true
|
||||
|
||||
|
||||
|
||||
# Packages.
|
||||
#pagetop-admin = { version = "0.0", path = "../pagetop-admin" }
|
||||
#pagetop-user = { version = "0.0", path = "../pagetop-user" }
|
||||
#pagetop-node = { version = "0.0", path = "../pagetop-node" }
|
||||
# Themes.
|
||||
#pagetop-bootsier = { version = "0.0", path = "../pagetop-bootsier" }
|
||||
#pagetop-bulmix = { version = "0.0", path = "../pagetop-bulmix" }
|
||||
|
||||
#[features]
|
||||
#default = [ "mysql" ]
|
||||
#mysql = [
|
||||
# "pagetop-user/mysql",
|
||||
# "pagetop-node/mysql",
|
||||
#]
|
||||
#postgres = [
|
||||
# "pagetop-user/postgres",
|
||||
# "pagetop-node/postgres",
|
||||
#]
|
||||
#sqlite = [
|
||||
# "pagetop-user/sqlite",
|
||||
# "pagetop-node/sqlite",
|
||||
#]
|
||||
6
packages/drust/config/common.toml
Normal file
6
packages/drust/config/common.toml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
[app]
|
||||
name = "Drust"
|
||||
description = "A modern web Content Management System to share your world."
|
||||
|
||||
[database]
|
||||
db_type = "mysql"
|
||||
10
packages/drust/config/default.toml
Normal file
10
packages/drust/config/default.toml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
[app]
|
||||
#theme = "Basic"
|
||||
#theme = "Chassis"
|
||||
theme = "Inception"
|
||||
#theme = "Bootsier"
|
||||
#theme = "Bulmix"
|
||||
language = "es-ES"
|
||||
|
||||
[log]
|
||||
tracing = "Info,pagetop=Debug,sqlx::query=Warn"
|
||||
7
packages/drust/config/local.default.toml
Normal file
7
packages/drust/config/local.default.toml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
[database]
|
||||
db_name = "drust"
|
||||
db_user = "drust"
|
||||
db_pass = "demo"
|
||||
|
||||
[dev]
|
||||
pagetop_project_dir = "/home/manuelcillero/Proyectos/pagetop"
|
||||
22
packages/drust/src/main.rs
Normal file
22
packages/drust/src/main.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
use pagetop::prelude::*;
|
||||
|
||||
struct Drust;
|
||||
|
||||
impl PackageTrait for Drust {
|
||||
fn dependencies(&self) -> Vec<PackageRef> {
|
||||
vec![
|
||||
// Packages.
|
||||
//&pagetop_admin::Admin,
|
||||
//&pagetop_user::User,
|
||||
//&pagetop_node::Node,
|
||||
// Themes.
|
||||
//&pagetop_bootsier::Bootsier,
|
||||
//&pagetop_bulmix::Bulmix,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[pagetop::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
Application::prepare(&Drust).run()?.await
|
||||
}
|
||||
47
packages/pagetop/Cargo.toml
Normal file
47
packages/pagetop/Cargo.toml
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
[package]
|
||||
name = "pagetop"
|
||||
version = "0.0.56"
|
||||
edition = "2021"
|
||||
|
||||
description = "An opinionated web framework to build modular Server-Side Rendering web solutions."
|
||||
|
||||
categories = ["web-programming", "gui", "development-tools", "asynchronous"]
|
||||
keywords = ["pagetop", "web", "framework", "frontend", "ssr"]
|
||||
readme = "../../README.md"
|
||||
|
||||
homepage = { workspace = true }
|
||||
repository = { workspace = true }
|
||||
license = { workspace = true }
|
||||
authors = { workspace = true }
|
||||
|
||||
[lib]
|
||||
name = "pagetop"
|
||||
|
||||
[dependencies]
|
||||
concat-string = "1.0.1"
|
||||
figlet-rs = "0.1.5"
|
||||
paste = "1.0.15"
|
||||
terminal_size = "0.4.0"
|
||||
toml = "0.8.19"
|
||||
|
||||
serde.workspace = true
|
||||
static-files.workspace = true
|
||||
|
||||
pagetop-macros.workspace = true
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
actix-web.workspace = true
|
||||
actix-web-files.workspace = true
|
||||
actix-web-static-files.workspace = true
|
||||
actix-session.workspace = true
|
||||
fluent-templates.workspace = true
|
||||
nom.workspace = true
|
||||
tracing.workspace = true
|
||||
tracing-appender.workspace = true
|
||||
tracing-subscriber.workspace = true
|
||||
tracing-actix-web.workspace = true
|
||||
substring.workspace = true
|
||||
unic-langid.workspace = true
|
||||
|
|
@ -3,9 +3,6 @@
|
|||
mod figfont;
|
||||
|
||||
use crate::core::{package, package::PackageRef};
|
||||
use crate::html::Markup;
|
||||
use crate::response::page::{ErrorPage, ResultPage};
|
||||
use crate::service::HttpRequest;
|
||||
use crate::{global, locale, service, trace};
|
||||
|
||||
use actix_session::config::{BrowserSession, PersistentSession, SessionLifecycle};
|
||||
|
|
@ -147,12 +144,12 @@ impl Application {
|
|||
InitError = (),
|
||||
>,
|
||||
> {
|
||||
service::App::new()
|
||||
.configure(package::all::configure_services)
|
||||
.default_service(service::web::route().to(service_not_found))
|
||||
service::App::new().configure(package::all::configure_services)
|
||||
// .default_service(service::web::route().to(service_not_found))
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
async fn service_not_found(request: HttpRequest) -> ResultPage<Markup, ErrorPage> {
|
||||
Err(ErrorPage::NotFound(request))
|
||||
}
|
||||
*/
|
||||
|
|
@ -120,53 +120,52 @@ mod path;
|
|||
mod source;
|
||||
mod value;
|
||||
|
||||
use crate::concat_string;
|
||||
use crate::config::data::ConfigData;
|
||||
use crate::config::file::File;
|
||||
use crate::join;
|
||||
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use std::env;
|
||||
use std::path::Path;
|
||||
|
||||
/// Directorio donde se encuentran los archivos de configuración.
|
||||
const CONFIG_DIR: &str = "config";
|
||||
|
||||
/// Valores originales de la configuración en forma de pares `clave = valor` recogidos de los
|
||||
/// archivos de configuración.
|
||||
|
||||
#[rustfmt::skip]
|
||||
/// Original configuration values in `key = value` pairs gathered from configuration files.
|
||||
pub static CONFIG_DATA: LazyLock<ConfigData> = LazyLock::new(|| {
|
||||
// Modo de ejecución según la variable de entorno PAGETOP_RUN_MODE. Por defecto 'default'.
|
||||
// Identify the configuration directory.
|
||||
let config_dir = env::var("CARGO_MANIFEST_DIR")
|
||||
.map(|manifest_dir| {
|
||||
let manifest_config = Path::new(&manifest_dir).join("config");
|
||||
if manifest_config.exists() {
|
||||
manifest_config.to_string_lossy().to_string()
|
||||
} else {
|
||||
"config".to_string()
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|_| "config".to_string());
|
||||
|
||||
// Execution mode based on the environment variable PAGETOP_RUN_MODE, defaults to 'default'.
|
||||
let run_mode = env::var("PAGETOP_RUN_MODE").unwrap_or_else(|_| "default".into());
|
||||
|
||||
// Inicializa los ajustes.
|
||||
// Initialize settings.
|
||||
let mut settings = ConfigData::default();
|
||||
|
||||
// Combina los archivos (opcionales) de configuración y asigna el modo de ejecución.
|
||||
// Merge (optional) configuration files and set the execution mode.
|
||||
settings
|
||||
// Primero añade la configuración común a todos los entornos. Por defecto 'common.toml'.
|
||||
.merge(
|
||||
File::with_name(&concat_string!(CONFIG_DIR, "/common.toml"))
|
||||
.required(false)
|
||||
).unwrap()
|
||||
// Añade la configuración específica del entorno. Por defecto 'default.toml'.
|
||||
.merge(
|
||||
File::with_name(&concat_string!(CONFIG_DIR, "/", run_mode, ".toml"))
|
||||
.required(false)
|
||||
).unwrap()
|
||||
// Añade la configuración local reservada del entorno. Por defecto 'local.default.toml'.
|
||||
.merge(
|
||||
File::with_name(&concat_string!(CONFIG_DIR, "/local.", run_mode, ".toml"))
|
||||
.required(false),
|
||||
).unwrap()
|
||||
// Añade la configuración local reservada general. Por defecto 'local.toml'.
|
||||
.merge(
|
||||
File::with_name(&concat_string!(CONFIG_DIR, "/local.toml"))
|
||||
.required(false)
|
||||
).unwrap()
|
||||
// Salvaguarda el modo de ejecución.
|
||||
// First, add the common configuration for all environments. Defaults to 'common.toml'.
|
||||
.merge(File::with_name(&join!(config_dir, "/common.toml")).required(false))
|
||||
.expect("Failed to merge common configuration (common.toml)")
|
||||
// Add the environment-specific configuration. Defaults to 'default.toml'.
|
||||
.merge(File::with_name(&join!(config_dir, "/", run_mode, ".toml")).required(false))
|
||||
.expect(&format!("Failed to merge {run_mode}.toml configuration"))
|
||||
// Add reserved local configuration for the environment. Defaults to 'local.default.toml'.
|
||||
.merge(File::with_name(&join!(config_dir, "/local.", run_mode, ".toml")).required(false))
|
||||
.expect("Failed to merge reserved local environment configuration")
|
||||
// Add the general reserved local configuration. Defaults to 'local.toml'.
|
||||
.merge(File::with_name(&join!(config_dir, "/local.toml")).required(false))
|
||||
.expect("Failed to merge general reserved local configuration")
|
||||
// Save the execution mode.
|
||||
.set("app.run_mode", run_mode)
|
||||
.unwrap();
|
||||
.expect("Failed to set application run mode");
|
||||
|
||||
settings
|
||||
});
|
||||
|
|
@ -69,11 +69,5 @@ impl<T: ?Sized + AnyBase> AnyTo for T {}
|
|||
// API to define functions that alter the behavior of PageTop core.
|
||||
pub mod action;
|
||||
|
||||
// API to build new components.
|
||||
pub mod component;
|
||||
|
||||
// API to add new features with packages.
|
||||
pub mod package;
|
||||
|
||||
// API to add new layouts with themes.
|
||||
pub mod theme;
|
||||
|
|
@ -1,11 +1,10 @@
|
|||
use crate::core::action::add_action;
|
||||
use crate::core::package::PackageRef;
|
||||
use crate::core::theme::all::THEMES;
|
||||
use crate::{global, service, static_files, static_files_service, trace};
|
||||
use crate::{service, trace};
|
||||
|
||||
use std::sync::{LazyLock, RwLock};
|
||||
|
||||
static_files!(base);
|
||||
//static_files!(base);
|
||||
|
||||
// PACKAGES ****************************************************************************************
|
||||
|
||||
|
|
@ -22,12 +21,12 @@ pub fn register_packages(root_package: Option<PackageRef>) {
|
|||
let mut enabled_list: Vec<PackageRef> = Vec::new();
|
||||
|
||||
// Add default welcome page package to the enabled list.
|
||||
add_to_enabled(&mut enabled_list, &crate::base::package::Welcome);
|
||||
// add_to_enabled(&mut enabled_list, &crate::base::package::Welcome);
|
||||
|
||||
// Add default theme packages to the enabled list.
|
||||
add_to_enabled(&mut enabled_list, &crate::base::theme::Basic);
|
||||
add_to_enabled(&mut enabled_list, &crate::base::theme::Chassis);
|
||||
add_to_enabled(&mut enabled_list, &crate::base::theme::Inception);
|
||||
// add_to_enabled(&mut enabled_list, &crate::base::theme::Basic);
|
||||
// add_to_enabled(&mut enabled_list, &crate::base::theme::Chassis);
|
||||
// add_to_enabled(&mut enabled_list, &crate::base::theme::Inception);
|
||||
|
||||
// If a root package is provided, add it to the enabled list.
|
||||
if let Some(package) = root_package {
|
||||
|
|
@ -61,7 +60,7 @@ fn add_to_enabled(list: &mut Vec<PackageRef>, package: PackageRef) {
|
|||
add_to_enabled(list, *d);
|
||||
}
|
||||
|
||||
// Check if the package has an associated theme to register.
|
||||
/* Check if the package has an associated theme to register.
|
||||
if let Some(theme) = package.theme() {
|
||||
let mut registered_themes = THEMES.write().unwrap();
|
||||
// Ensure the theme is not already registered to avoid duplicates.
|
||||
|
|
@ -74,7 +73,7 @@ fn add_to_enabled(list: &mut Vec<PackageRef>, package: PackageRef) {
|
|||
}
|
||||
} else {
|
||||
trace::debug!("Enabling \"{}\" package", package.short_name());
|
||||
}
|
||||
} */
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -130,11 +129,13 @@ pub fn init_packages() {
|
|||
// CONFIGURE SERVICES ******************************************************************************
|
||||
|
||||
pub fn configure_services(scfg: &mut service::web::ServiceConfig) {
|
||||
/*
|
||||
static_files_service!(
|
||||
scfg,
|
||||
base => "/base",
|
||||
[&global::SETTINGS.dev.pagetop_project_dir, "static/base"]
|
||||
);
|
||||
*/
|
||||
for m in ENABLED_PACKAGES.read().unwrap().iter() {
|
||||
m.configure_service(scfg);
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
use crate::core::action::ActionBox;
|
||||
use crate::core::theme::ThemeRef;
|
||||
use crate::core::AnyBase;
|
||||
use crate::locale::L10n;
|
||||
use crate::{actions, service};
|
||||
|
|
@ -16,10 +15,6 @@ pub trait PackageTrait: AnyBase + Send + Sync {
|
|||
L10n::none()
|
||||
}
|
||||
|
||||
fn theme(&self) -> Option<ThemeRef> {
|
||||
None
|
||||
}
|
||||
|
||||
fn dependencies(&self) -> Vec<PackageRef> {
|
||||
vec![]
|
||||
}
|
||||
|
|
@ -76,12 +76,12 @@
|
|||
// RE-EXPORTED MACROS AND DERIVES.
|
||||
// *************************************************************************************************
|
||||
|
||||
pub use concat_string::concat_string;
|
||||
pub use concat_string::concat_string as join;
|
||||
|
||||
/// Enables flexible identifier concatenation in macros, allowing new items with pasted identifiers.
|
||||
pub use paste::paste;
|
||||
|
||||
pub use pagetop_macros::{fn_builder, main, test, AutoDefault, ComponentClasses};
|
||||
pub use pagetop_macros::{main, test, AutoDefault};
|
||||
|
||||
// *************************************************************************************************
|
||||
// GLOBAL.
|
||||
|
|
@ -108,12 +108,8 @@ static_locales!(LOCALES_PAGETOP);
|
|||
pub mod config;
|
||||
// Application tracing and event logging.
|
||||
pub mod trace;
|
||||
// HTML in code.
|
||||
pub mod html;
|
||||
// Localization.
|
||||
pub mod locale;
|
||||
// Date and time handling.
|
||||
pub mod datetime;
|
||||
|
||||
// Essential web framework.
|
||||
pub mod service;
|
||||
|
|
@ -124,9 +120,6 @@ pub mod core;
|
|||
// Web request response variants.
|
||||
pub mod response;
|
||||
|
||||
// Base actions, components, packages, and themes.
|
||||
pub mod base;
|
||||
|
||||
// Prepare and run the application.
|
||||
pub mod app;
|
||||
|
||||
|
|
@ -86,7 +86,6 @@
|
|||
//! static_locales!(LOCALES_SAMPLE in "path/to/locale");
|
||||
//! ```
|
||||
|
||||
use crate::html::{Markup, PreEscaped};
|
||||
use crate::{global, kv, AutoDefault, LOCALES_PAGETOP};
|
||||
|
||||
pub use fluent_templates;
|
||||
|
|
@ -240,10 +239,6 @@ impl L10n {
|
|||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn escaped(&self, langid: &LanguageIdentifier) -> Markup {
|
||||
PreEscaped(self.using(langid).unwrap_or_default())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for L10n {
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
//! The `PageTop` Prelude.
|
||||
|
||||
// RE-EXPORTED MACROS AND DERIVES.
|
||||
pub use crate::{concat_string, fn_builder, main, paste, test};
|
||||
pub use crate::{AutoDefault, ComponentClasses};
|
||||
// RE-EXPORTED.
|
||||
pub use crate::{join, main, paste, test, AutoDefault};
|
||||
|
||||
// GLOBAL.
|
||||
pub use crate::{global, HashMapResources, TypeId, Weight};
|
||||
|
|
@ -13,8 +12,6 @@ pub use crate::{global, HashMapResources, TypeId, Weight};
|
|||
pub use crate::kv;
|
||||
// crate::config
|
||||
pub use crate::config_defaults;
|
||||
// crate::html
|
||||
pub use crate::html;
|
||||
// crate::locale
|
||||
pub use crate::static_locales;
|
||||
// crate::service
|
||||
|
|
@ -26,26 +23,16 @@ pub use crate::actions;
|
|||
|
||||
pub use crate::trace;
|
||||
|
||||
pub use crate::html::*;
|
||||
|
||||
pub use crate::locale::*;
|
||||
|
||||
pub use crate::datetime::*;
|
||||
|
||||
pub use crate::service;
|
||||
pub use crate::service::{HttpMessage, HttpRequest};
|
||||
|
||||
pub use crate::core::{AnyBase, AnyTo};
|
||||
|
||||
pub use crate::core::action::*;
|
||||
pub use crate::core::component::*;
|
||||
pub use crate::core::package::*;
|
||||
pub use crate::core::theme::*;
|
||||
|
||||
pub use crate::response::{json::*, page::*, redirect::*, ResponseError};
|
||||
|
||||
pub use crate::base::action;
|
||||
pub use crate::base::component::*;
|
||||
pub use crate::base::theme;
|
||||
pub use crate::response::{json::*, redirect::*, ResponseError};
|
||||
|
||||
pub use crate::app::Application;
|
||||
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
pub use actix_web::ResponseError;
|
||||
|
||||
pub mod page;
|
||||
|
||||
pub mod json;
|
||||
|
||||
pub mod redirect;
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
//! Base actions, components, packages, and themes.
|
||||
|
||||
pub mod action;
|
||||
|
||||
pub mod component;
|
||||
|
||||
pub mod package;
|
||||
|
||||
pub mod theme;
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
pub type FnActionWithComponent<C> = fn(component: &mut C, cx: &mut Context);
|
||||
|
||||
pub mod page;
|
||||
|
||||
pub mod theme;
|
||||
|
||||
pub mod component;
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
mod is_renderable;
|
||||
pub use is_renderable::*;
|
||||
|
||||
mod before_prepare_component;
|
||||
pub use before_prepare_component::*;
|
||||
|
||||
mod after_prepare_component;
|
||||
pub use after_prepare_component::*;
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
use crate::base::action::FnActionWithComponent;
|
||||
|
||||
pub struct AfterPrepare<C: ComponentTrait> {
|
||||
f: FnActionWithComponent<C>,
|
||||
referer_type_id: Option<TypeId>,
|
||||
referer_id: OptionId,
|
||||
weight: Weight,
|
||||
}
|
||||
|
||||
impl<C: ComponentTrait> ActionTrait for AfterPrepare<C> {
|
||||
fn referer_type_id(&self) -> Option<TypeId> {
|
||||
self.referer_type_id
|
||||
}
|
||||
|
||||
fn referer_id(&self) -> Option<String> {
|
||||
self.referer_id.get()
|
||||
}
|
||||
|
||||
fn weight(&self) -> Weight {
|
||||
self.weight
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: ComponentTrait> AfterPrepare<C> {
|
||||
pub fn new(f: FnActionWithComponent<C>) -> Self {
|
||||
AfterPrepare {
|
||||
f,
|
||||
referer_type_id: Some(TypeId::of::<C>()),
|
||||
referer_id: OptionId::default(),
|
||||
weight: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn filter_by_referer_id(mut self, id: impl Into<String>) -> Self {
|
||||
self.referer_id.set_value(id);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_weight(mut self, value: Weight) -> Self {
|
||||
self.weight = value;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[allow(clippy::inline_always)]
|
||||
pub(crate) fn dispatch(component: &mut C, cx: &mut Context) {
|
||||
dispatch_actions(
|
||||
&ActionKey::new(TypeId::of::<Self>(), None, Some(TypeId::of::<C>()), None),
|
||||
|action: &Self| (action.f)(component, cx),
|
||||
);
|
||||
if let Some(id) = component.id() {
|
||||
dispatch_actions(
|
||||
&ActionKey::new(
|
||||
TypeId::of::<Self>(),
|
||||
None,
|
||||
Some(TypeId::of::<C>()),
|
||||
Some(id),
|
||||
),
|
||||
|action: &Self| (action.f)(component, cx),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
use crate::base::action::FnActionWithComponent;
|
||||
|
||||
pub struct BeforePrepare<C: ComponentTrait> {
|
||||
f: FnActionWithComponent<C>,
|
||||
referer_type_id: Option<TypeId>,
|
||||
referer_id: OptionId,
|
||||
weight: Weight,
|
||||
}
|
||||
|
||||
impl<C: ComponentTrait> ActionTrait for BeforePrepare<C> {
|
||||
fn referer_type_id(&self) -> Option<TypeId> {
|
||||
self.referer_type_id
|
||||
}
|
||||
|
||||
fn referer_id(&self) -> Option<String> {
|
||||
self.referer_id.get()
|
||||
}
|
||||
|
||||
fn weight(&self) -> Weight {
|
||||
self.weight
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: ComponentTrait> BeforePrepare<C> {
|
||||
pub fn new(f: FnActionWithComponent<C>) -> Self {
|
||||
BeforePrepare {
|
||||
f,
|
||||
referer_type_id: Some(TypeId::of::<C>()),
|
||||
referer_id: OptionId::default(),
|
||||
weight: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn filter_by_referer_id(mut self, id: impl Into<String>) -> Self {
|
||||
self.referer_id.set_value(id);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_weight(mut self, value: Weight) -> Self {
|
||||
self.weight = value;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[allow(clippy::inline_always)]
|
||||
pub(crate) fn dispatch(component: &mut C, cx: &mut Context) {
|
||||
dispatch_actions(
|
||||
&ActionKey::new(TypeId::of::<Self>(), None, Some(TypeId::of::<C>()), None),
|
||||
|action: &Self| (action.f)(component, cx),
|
||||
);
|
||||
if let Some(id) = component.id() {
|
||||
dispatch_actions(
|
||||
&ActionKey::new(
|
||||
TypeId::of::<Self>(),
|
||||
None,
|
||||
Some(TypeId::of::<C>()),
|
||||
Some(id),
|
||||
),
|
||||
|action: &Self| (action.f)(component, cx),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
pub type FnIsRenderable<C> = fn(component: &C, cx: &mut Context) -> bool;
|
||||
|
||||
pub struct IsRenderable<C: ComponentTrait> {
|
||||
f: FnIsRenderable<C>,
|
||||
referer_type_id: Option<TypeId>,
|
||||
referer_id: OptionId,
|
||||
weight: Weight,
|
||||
}
|
||||
|
||||
impl<C: ComponentTrait> ActionTrait for IsRenderable<C> {
|
||||
fn referer_type_id(&self) -> Option<TypeId> {
|
||||
self.referer_type_id
|
||||
}
|
||||
|
||||
fn referer_id(&self) -> Option<String> {
|
||||
self.referer_id.get()
|
||||
}
|
||||
|
||||
fn weight(&self) -> Weight {
|
||||
self.weight
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: ComponentTrait> IsRenderable<C> {
|
||||
pub fn new(f: FnIsRenderable<C>) -> Self {
|
||||
IsRenderable {
|
||||
f,
|
||||
referer_type_id: Some(TypeId::of::<C>()),
|
||||
referer_id: OptionId::default(),
|
||||
weight: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn filter_by_referer_id(mut self, id: impl Into<String>) -> Self {
|
||||
self.referer_id.set_value(id);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_weight(mut self, value: Weight) -> Self {
|
||||
self.weight = value;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[allow(clippy::inline_always)]
|
||||
pub(crate) fn dispatch(component: &C, cx: &mut Context) -> bool {
|
||||
let mut renderable = true;
|
||||
dispatch_actions(
|
||||
&ActionKey::new(TypeId::of::<Self>(), None, Some(TypeId::of::<C>()), None),
|
||||
|action: &Self| {
|
||||
if renderable && !(action.f)(component, cx) {
|
||||
renderable = false;
|
||||
}
|
||||
},
|
||||
);
|
||||
if renderable {
|
||||
if let Some(id) = component.id() {
|
||||
dispatch_actions(
|
||||
&ActionKey::new(
|
||||
TypeId::of::<Self>(),
|
||||
None,
|
||||
Some(TypeId::of::<C>()),
|
||||
Some(id),
|
||||
),
|
||||
|action: &Self| {
|
||||
if renderable && !(action.f)(component, cx) {
|
||||
renderable = false;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
renderable
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
mod before_prepare_body;
|
||||
pub use before_prepare_body::*;
|
||||
|
||||
mod after_prepare_body;
|
||||
pub use after_prepare_body::*;
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
pub type FnAfterPrepareBody = fn(page: &mut Page);
|
||||
|
||||
pub struct AfterPrepareBody {
|
||||
f: FnAfterPrepareBody,
|
||||
weight: Weight,
|
||||
}
|
||||
|
||||
impl ActionTrait for AfterPrepareBody {
|
||||
fn weight(&self) -> Weight {
|
||||
self.weight
|
||||
}
|
||||
}
|
||||
|
||||
impl AfterPrepareBody {
|
||||
pub fn new(f: FnAfterPrepareBody) -> Self {
|
||||
AfterPrepareBody { f, weight: 0 }
|
||||
}
|
||||
|
||||
pub fn with_weight(mut self, value: Weight) -> Self {
|
||||
self.weight = value;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[allow(clippy::inline_always)]
|
||||
pub(crate) fn dispatch(page: &mut Page) {
|
||||
dispatch_actions(
|
||||
&ActionKey::new(TypeId::of::<Self>(), None, None, None),
|
||||
|action: &Self| (action.f)(page),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
pub type FnBeforePrepareBody = fn(page: &mut Page);
|
||||
|
||||
pub struct BeforePrepareBody {
|
||||
f: FnBeforePrepareBody,
|
||||
weight: Weight,
|
||||
}
|
||||
|
||||
impl ActionTrait for BeforePrepareBody {
|
||||
fn weight(&self) -> Weight {
|
||||
self.weight
|
||||
}
|
||||
}
|
||||
|
||||
impl BeforePrepareBody {
|
||||
pub fn new(f: FnBeforePrepareBody) -> Self {
|
||||
BeforePrepareBody { f, weight: 0 }
|
||||
}
|
||||
|
||||
pub fn with_weight(mut self, value: Weight) -> Self {
|
||||
self.weight = value;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[allow(clippy::inline_always)]
|
||||
pub(crate) fn dispatch(page: &mut Page) {
|
||||
dispatch_actions(
|
||||
&ActionKey::new(TypeId::of::<Self>(), None, None, None),
|
||||
|action: &Self| (action.f)(page),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
mod before_prepare_component;
|
||||
pub use before_prepare_component::*;
|
||||
|
||||
mod after_prepare_component;
|
||||
pub use after_prepare_component::*;
|
||||
|
||||
mod render_component;
|
||||
pub use render_component::*;
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
use crate::base::action::FnActionWithComponent;
|
||||
|
||||
pub struct AfterPrepare<C: ComponentTrait> {
|
||||
f: FnActionWithComponent<C>,
|
||||
theme_type_id: Option<TypeId>,
|
||||
referer_type_id: Option<TypeId>,
|
||||
}
|
||||
|
||||
impl<C: ComponentTrait> ActionTrait for AfterPrepare<C> {
|
||||
fn theme_type_id(&self) -> Option<TypeId> {
|
||||
self.theme_type_id
|
||||
}
|
||||
|
||||
fn referer_type_id(&self) -> Option<TypeId> {
|
||||
self.referer_type_id
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: ComponentTrait> AfterPrepare<C> {
|
||||
pub fn new(theme: ThemeRef, f: FnActionWithComponent<C>) -> Self {
|
||||
AfterPrepare {
|
||||
f,
|
||||
theme_type_id: Some(theme.type_id()),
|
||||
referer_type_id: Some(TypeId::of::<C>()),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[allow(clippy::inline_always)]
|
||||
pub(crate) fn dispatch(component: &mut C, cx: &mut Context) {
|
||||
dispatch_actions(
|
||||
&ActionKey::new(
|
||||
TypeId::of::<Self>(),
|
||||
Some(cx.theme().type_id()),
|
||||
Some(TypeId::of::<C>()),
|
||||
None,
|
||||
),
|
||||
|action: &Self| (action.f)(component, cx),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
use crate::base::action::FnActionWithComponent;
|
||||
|
||||
pub struct BeforePrepare<C: ComponentTrait> {
|
||||
f: FnActionWithComponent<C>,
|
||||
theme_type_id: Option<TypeId>,
|
||||
referer_type_id: Option<TypeId>,
|
||||
}
|
||||
|
||||
impl<C: ComponentTrait> ActionTrait for BeforePrepare<C> {
|
||||
fn theme_type_id(&self) -> Option<TypeId> {
|
||||
self.theme_type_id
|
||||
}
|
||||
|
||||
fn referer_type_id(&self) -> Option<TypeId> {
|
||||
self.referer_type_id
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: ComponentTrait> BeforePrepare<C> {
|
||||
pub fn new(theme: ThemeRef, f: FnActionWithComponent<C>) -> Self {
|
||||
BeforePrepare {
|
||||
f,
|
||||
theme_type_id: Some(theme.type_id()),
|
||||
referer_type_id: Some(TypeId::of::<C>()),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[allow(clippy::inline_always)]
|
||||
pub(crate) fn dispatch(component: &mut C, cx: &mut Context) {
|
||||
dispatch_actions(
|
||||
&ActionKey::new(
|
||||
TypeId::of::<Self>(),
|
||||
Some(cx.theme().type_id()),
|
||||
Some(TypeId::of::<C>()),
|
||||
None,
|
||||
),
|
||||
|action: &Self| (action.f)(component, cx),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
pub type FnRenderComponent<C> = fn(component: &C, cx: &mut Context) -> Option<Markup>;
|
||||
|
||||
pub struct RenderComponent<C: ComponentTrait> {
|
||||
f: FnRenderComponent<C>,
|
||||
theme_type_id: Option<TypeId>,
|
||||
referer_type_id: Option<TypeId>,
|
||||
}
|
||||
|
||||
impl<C: ComponentTrait> ActionTrait for RenderComponent<C> {
|
||||
fn theme_type_id(&self) -> Option<TypeId> {
|
||||
self.theme_type_id
|
||||
}
|
||||
|
||||
fn referer_type_id(&self) -> Option<TypeId> {
|
||||
self.referer_type_id
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: ComponentTrait> RenderComponent<C> {
|
||||
pub fn new(theme: ThemeRef, f: FnRenderComponent<C>) -> Self {
|
||||
RenderComponent {
|
||||
f,
|
||||
theme_type_id: Some(theme.type_id()),
|
||||
referer_type_id: Some(TypeId::of::<C>()),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[allow(clippy::inline_always)]
|
||||
pub(crate) fn dispatch(component: &C, cx: &mut Context) -> Option<Markup> {
|
||||
let mut render_component: Option<Markup> = None;
|
||||
dispatch_actions(
|
||||
&ActionKey::new(
|
||||
TypeId::of::<Self>(),
|
||||
Some(cx.theme().type_id()),
|
||||
Some(TypeId::of::<C>()),
|
||||
None,
|
||||
),
|
||||
|action: &Self| {
|
||||
if render_component.is_none() {
|
||||
render_component = (action.f)(component, cx);
|
||||
}
|
||||
},
|
||||
);
|
||||
render_component
|
||||
}
|
||||
}
|
||||
|
|
@ -1,201 +0,0 @@
|
|||
use crate::core::component::{AssetsOp, Context};
|
||||
use crate::html::{JavaScript, StyleSheet};
|
||||
use crate::{AutoDefault, Weight};
|
||||
|
||||
use std::fmt;
|
||||
|
||||
// Context parameters.
|
||||
pub const PARAM_BASE_WEIGHT: &str = "base.weight";
|
||||
pub const PARAM_BASE_INCLUDE_ICONS: &str = "base.include.icon";
|
||||
pub const PARAM_BASE_INCLUDE_FLEX_ASSETS: &str = "base.include.flex";
|
||||
pub const PARAM_BASE_INCLUDE_MENU_ASSETS: &str = "base.include.menu";
|
||||
|
||||
pub(crate) fn add_base_assets(cx: &mut Context) {
|
||||
let weight = cx.get_param::<Weight>(PARAM_BASE_WEIGHT).unwrap_or(-90);
|
||||
|
||||
cx.set_assets(AssetsOp::AddStyleSheet(
|
||||
StyleSheet::from("/base/css/root.css")
|
||||
.with_version("0.0.1")
|
||||
.with_weight(weight),
|
||||
))
|
||||
.set_assets(AssetsOp::AddStyleSheet(
|
||||
StyleSheet::from("/base/css/looks.css")
|
||||
.with_version("0.0.2")
|
||||
.with_weight(weight),
|
||||
))
|
||||
.set_assets(AssetsOp::AddStyleSheet(
|
||||
StyleSheet::from("/base/css/buttons.css")
|
||||
.with_version("0.0.2")
|
||||
.with_weight(weight),
|
||||
));
|
||||
|
||||
if let Ok(true) = cx.get_param::<bool>(PARAM_BASE_INCLUDE_ICONS) {
|
||||
cx.set_assets(AssetsOp::AddStyleSheet(
|
||||
StyleSheet::from("/base/css/icons.min.css")
|
||||
.with_version("1.11.1")
|
||||
.with_weight(weight),
|
||||
));
|
||||
}
|
||||
|
||||
if let Ok(true) = cx.get_param::<bool>(PARAM_BASE_INCLUDE_FLEX_ASSETS) {
|
||||
cx.set_assets(AssetsOp::AddStyleSheet(
|
||||
StyleSheet::from("/base/css/flex.css")
|
||||
.with_version("0.0.1")
|
||||
.with_weight(weight),
|
||||
));
|
||||
}
|
||||
|
||||
if let Ok(true) = cx.get_param::<bool>(PARAM_BASE_INCLUDE_MENU_ASSETS) {
|
||||
cx.set_assets(AssetsOp::AddStyleSheet(
|
||||
StyleSheet::from("/base/css/menu.css")
|
||||
.with_version("0.0.1")
|
||||
.with_weight(weight),
|
||||
))
|
||||
.set_assets(AssetsOp::AddJavaScript(
|
||||
JavaScript::defer("/base/js/menu.js")
|
||||
.with_version("0.0.1")
|
||||
.with_weight(weight),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// *************************************************************************************************
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[derive(AutoDefault)]
|
||||
pub enum BreakPoint {
|
||||
#[default]
|
||||
None, // Does not apply. Rest initially assume 1 pixel = 0.0625rem
|
||||
SM, // @media screen and [ (max-width: 35.5rem) <= 568px < (min-width: 35.5625rem) ]
|
||||
MD, // @media screen and [ (max-width: 48rem) <= 768px < (min-width: 48.0625rem) ]
|
||||
LG, // @media screen and [ (max-width: 62rem) <= 992px < (min-width: 62.0625rem) ]
|
||||
XL, // @media screen and [ (max-width: 80rem) <= 1280px < (min-width: 80.0625rem) ]
|
||||
X2L, // @media screen and [ (max-width: 90rem) <= 1440px < (min-width: 90.0625rem) ]
|
||||
X3L, // @media screen and [ (max-width: 120rem) <= 1920px < (min-width: 120.0625rem) ]
|
||||
X2K, // @media screen and [ (max-width: 160rem) <= 2560px < (min-width: 160.0625rem) ]
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
impl fmt::Display for BreakPoint {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
BreakPoint::None => write!(f, "bp__none"),
|
||||
BreakPoint::SM => write!(f, "bp__sm"),
|
||||
BreakPoint::MD => write!(f, "bp__md"),
|
||||
BreakPoint::LG => write!(f, "bp__lg"),
|
||||
BreakPoint::XL => write!(f, "bp__xl"),
|
||||
BreakPoint::X2L => write!(f, "bp__x2l"),
|
||||
BreakPoint::X3L => write!(f, "bp__x3l"),
|
||||
BreakPoint::X2K => write!(f, "bp__x2k"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// *************************************************************************************************
|
||||
|
||||
#[derive(AutoDefault)]
|
||||
pub enum StyleBase {
|
||||
#[default]
|
||||
Default,
|
||||
Info,
|
||||
Success,
|
||||
Warning,
|
||||
Danger,
|
||||
Light,
|
||||
Dark,
|
||||
Link,
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
impl fmt::Display for StyleBase {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
StyleBase::Default => write!(f, "style__default"),
|
||||
StyleBase::Info => write!(f, "style__info"),
|
||||
StyleBase::Success => write!(f, "style__success"),
|
||||
StyleBase::Warning => write!(f, "style__warning"),
|
||||
StyleBase::Danger => write!(f, "style__danger"),
|
||||
StyleBase::Light => write!(f, "style__light"),
|
||||
StyleBase::Dark => write!(f, "style__dark"),
|
||||
StyleBase::Link => write!(f, "style__link"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// *************************************************************************************************
|
||||
|
||||
#[derive(AutoDefault)]
|
||||
pub enum FontSize {
|
||||
ExtraLarge,
|
||||
XxLarge,
|
||||
XLarge,
|
||||
Large,
|
||||
Medium,
|
||||
#[default]
|
||||
Normal,
|
||||
Small,
|
||||
XSmall,
|
||||
XxSmall,
|
||||
ExtraSmall,
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
impl fmt::Display for FontSize {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
FontSize::ExtraLarge => write!(f, "fs__x3l"),
|
||||
FontSize::XxLarge => write!(f, "fs__x2l"),
|
||||
FontSize::XLarge => write!(f, "fs__xl"),
|
||||
FontSize::Large => write!(f, "fs__l"),
|
||||
FontSize::Medium => write!(f, "fs__m"),
|
||||
FontSize::Normal => write!(f, ""),
|
||||
FontSize::Small => write!(f, "fs__s"),
|
||||
FontSize::XSmall => write!(f, "fs__xs"),
|
||||
FontSize::XxSmall => write!(f, "fs__x2s"),
|
||||
FontSize::ExtraSmall => write!(f, "fs__x3s"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// *************************************************************************************************
|
||||
|
||||
pub mod flex;
|
||||
|
||||
mod basic;
|
||||
pub use basic::*;
|
||||
|
||||
mod error403;
|
||||
pub use error403::Error403;
|
||||
|
||||
mod error404;
|
||||
pub use error404::Error404;
|
||||
|
||||
mod heading;
|
||||
pub use heading::{Heading, HeadingSize, HeadingType};
|
||||
|
||||
mod paragraph;
|
||||
pub use paragraph::Paragraph;
|
||||
|
||||
mod icon;
|
||||
pub use icon::Icon;
|
||||
|
||||
mod button;
|
||||
pub use button::{Button, ButtonTarget};
|
||||
|
||||
mod image;
|
||||
pub use image::{Image, ImageSize};
|
||||
|
||||
mod block;
|
||||
pub use block::Block;
|
||||
|
||||
mod branding;
|
||||
pub use branding::Branding;
|
||||
|
||||
mod powered_by;
|
||||
pub use powered_by::{PoweredBy, PoweredByLogo};
|
||||
|
||||
pub mod menu;
|
||||
pub use menu::Menu;
|
||||
|
||||
pub mod form;
|
||||
pub use form::{Form, FormMethod};
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
mod html;
|
||||
pub use html::Html;
|
||||
|
||||
mod fluent;
|
||||
pub use fluent::Fluent;
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
#[derive(AutoDefault)]
|
||||
pub struct Fluent(L10n);
|
||||
|
||||
impl ComponentTrait for Fluent {
|
||||
fn new() -> Self {
|
||||
Fluent::default()
|
||||
}
|
||||
|
||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
||||
PrepareMarkup::With(self.0.escaped(cx.langid()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Fluent {
|
||||
pub fn with(l10n: L10n) -> Self {
|
||||
Fluent(l10n)
|
||||
}
|
||||
|
||||
pub fn set_l10n(&mut self, l10n: L10n) -> &mut Self {
|
||||
self.0 = l10n;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
#[derive(AutoDefault)]
|
||||
pub struct Html(Markup);
|
||||
|
||||
impl ComponentTrait for Html {
|
||||
fn new() -> Self {
|
||||
Html::default()
|
||||
}
|
||||
|
||||
fn prepare_component(&self, _cx: &mut Context) -> PrepareMarkup {
|
||||
PrepareMarkup::With(html! { (self.0) })
|
||||
}
|
||||
}
|
||||
|
||||
impl Html {
|
||||
pub fn with(html: Markup) -> Self {
|
||||
Html(html)
|
||||
}
|
||||
|
||||
pub fn set_html(&mut self, html: Markup) -> &mut Self {
|
||||
self.0 = html;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
@ -1,95 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[derive(AutoDefault, ComponentClasses)]
|
||||
pub struct Block {
|
||||
id : OptionId,
|
||||
classes: OptionClasses,
|
||||
style : StyleBase,
|
||||
title : OptionTranslated,
|
||||
mixed : MixedComponents,
|
||||
}
|
||||
|
||||
impl ComponentTrait for Block {
|
||||
fn new() -> Self {
|
||||
Block::default()
|
||||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.id.get()
|
||||
}
|
||||
|
||||
fn setup_before_prepare(&mut self, _cx: &mut Context) {
|
||||
self.set_classes(
|
||||
ClassesOp::Prepend,
|
||||
["block__container".to_string(), self.style().to_string()].join(" "),
|
||||
);
|
||||
}
|
||||
|
||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
||||
let block_body = self.components().render(cx);
|
||||
|
||||
if block_body.is_empty() {
|
||||
return PrepareMarkup::None;
|
||||
}
|
||||
|
||||
let id = cx.required_id::<Block>(self.id());
|
||||
|
||||
PrepareMarkup::With(html! {
|
||||
div id=(id) class=[self.classes().get()] {
|
||||
@if let Some(title) = self.title().using(cx.langid()) {
|
||||
h2 class="block__title" { (title) }
|
||||
}
|
||||
div class="block__content" { (block_body) }
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Block {
|
||||
// Block BUILDER.
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_id(&mut self, id: impl Into<String>) -> &mut Self {
|
||||
self.id.set_value(id);
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_style(&mut self, style: StyleBase) -> &mut Self {
|
||||
self.style = style;
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_title(&mut self, title: L10n) -> &mut Self {
|
||||
self.title.set_value(title);
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_components(&mut self, op: AnyOp) -> &mut Self {
|
||||
self.mixed.set_value(op);
|
||||
self
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub fn add_component(mut self, component: impl ComponentTrait) -> Self {
|
||||
self.mixed.set_value(AnyOp::Add(AnyComponent::with(component)));
|
||||
self
|
||||
}
|
||||
|
||||
// Block GETTERS.
|
||||
|
||||
pub fn style(&self) -> &StyleBase {
|
||||
&self.style
|
||||
}
|
||||
|
||||
pub fn title(&self) -> &OptionTranslated {
|
||||
&self.title
|
||||
}
|
||||
|
||||
pub fn components(&self) -> &MixedComponents {
|
||||
&self.mixed
|
||||
}
|
||||
}
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[derive(AutoDefault)]
|
||||
pub struct Branding {
|
||||
id : OptionId,
|
||||
#[default(_code = "global::SETTINGS.app.name.to_owned()")]
|
||||
app_name : String,
|
||||
slogan : OptionTranslated,
|
||||
logo : OptionComponent<Image>,
|
||||
#[default(_code = "|_| \"/\"")]
|
||||
frontpage: FnContextualPath,
|
||||
}
|
||||
|
||||
impl ComponentTrait for Branding {
|
||||
fn new() -> Self {
|
||||
Branding::default()
|
||||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.id.get()
|
||||
}
|
||||
|
||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
||||
let logo = self.logo().render(cx);
|
||||
let home = self.frontpage()(cx);
|
||||
let title = &L10n::l("site_home").using(cx.langid());
|
||||
PrepareMarkup::With(html! {
|
||||
div id=[self.id()] class="branding__container" {
|
||||
div class="branding__content" {
|
||||
@if !logo.is_empty() {
|
||||
a class="branding__logo" href=(home) title=[title] rel="home" {
|
||||
(logo)
|
||||
}
|
||||
}
|
||||
div class="branding__text" {
|
||||
a class="branding__name" href=(home) title=[title] rel="home" {
|
||||
(self.app_name())
|
||||
}
|
||||
@if let Some(slogan) = self.slogan().using(cx.langid()) {
|
||||
div class="branding__slogan" {
|
||||
(slogan)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Branding {
|
||||
// Branding BUILDER.
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_id(&mut self, id: impl Into<String>) -> &mut Self {
|
||||
self.id.set_value(id);
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_app_name(&mut self, app_name: impl Into<String>) -> &mut Self {
|
||||
self.app_name = app_name.into();
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_slogan(&mut self, slogan: L10n) -> &mut Self {
|
||||
self.slogan.set_value(slogan);
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_logo(&mut self, logo: Option<Image>) -> &mut Self {
|
||||
self.logo.set_value(logo);
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_frontpage(&mut self, frontpage: FnContextualPath) -> &mut Self {
|
||||
self.frontpage = frontpage;
|
||||
self
|
||||
}
|
||||
|
||||
// Branding GETTERS.
|
||||
|
||||
pub fn app_name(&self) -> &String {
|
||||
&self.app_name
|
||||
}
|
||||
|
||||
pub fn slogan(&self) -> &OptionTranslated {
|
||||
&self.slogan
|
||||
}
|
||||
|
||||
pub fn logo(&self) -> &OptionComponent<Image> {
|
||||
&self.logo
|
||||
}
|
||||
|
||||
pub fn frontpage(&self) -> &FnContextualPath {
|
||||
&self.frontpage
|
||||
}
|
||||
}
|
||||
|
|
@ -1,156 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
#[derive(AutoDefault)]
|
||||
pub enum ButtonTarget {
|
||||
#[default]
|
||||
Default,
|
||||
Blank,
|
||||
Parent,
|
||||
Top,
|
||||
Context(String),
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[derive(AutoDefault, ComponentClasses)]
|
||||
pub struct Button {
|
||||
id : OptionId,
|
||||
classes : OptionClasses,
|
||||
style : StyleBase,
|
||||
font_size : FontSize,
|
||||
left_icon : OptionComponent<Icon>,
|
||||
right_icon: OptionComponent<Icon>,
|
||||
href : OptionString,
|
||||
html : OptionTranslated,
|
||||
target : ButtonTarget,
|
||||
}
|
||||
|
||||
impl ComponentTrait for Button {
|
||||
fn new() -> Self {
|
||||
Button::default()
|
||||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.id.get()
|
||||
}
|
||||
|
||||
fn setup_before_prepare(&mut self, _cx: &mut Context) {
|
||||
self.set_classes(
|
||||
ClassesOp::Prepend,
|
||||
[
|
||||
"button__tap".to_string(),
|
||||
self.style().to_string(),
|
||||
self.font_size().to_string(),
|
||||
]
|
||||
.join(" "),
|
||||
);
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
||||
let target = match &self.target() {
|
||||
ButtonTarget::Default => None,
|
||||
ButtonTarget::Blank => Some("_blank"),
|
||||
ButtonTarget::Parent => Some("_parent"),
|
||||
ButtonTarget::Top => Some("_top"),
|
||||
ButtonTarget::Context(name) => Some(name.as_str()),
|
||||
};
|
||||
PrepareMarkup::With(html! {
|
||||
a
|
||||
id=[self.id()]
|
||||
class=[self.classes().get()]
|
||||
href=[self.href().get()]
|
||||
target=[target]
|
||||
{
|
||||
(self.left_icon().render(cx))
|
||||
span { (self.html().escaped(cx.langid())) }
|
||||
(self.right_icon().render(cx))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Button {
|
||||
pub fn anchor(href: impl Into<String>, html: L10n) -> Self {
|
||||
Button::default().with_href(href).with_html(html)
|
||||
}
|
||||
|
||||
// Button BUILDER.
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_id(&mut self, id: impl Into<String>) -> &mut Self {
|
||||
self.id.set_value(id);
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_style(&mut self, style: StyleBase) -> &mut Self {
|
||||
self.style = style;
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_font_size(&mut self, font_size: FontSize) -> &mut Self {
|
||||
self.font_size = font_size;
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_left_icon(&mut self, icon: Option<Icon>) -> &mut Self {
|
||||
self.left_icon.set_value(icon);
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_right_icon(&mut self, icon: Option<Icon>) -> &mut Self {
|
||||
self.right_icon.set_value(icon);
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_href(&mut self, href: impl Into<String>) -> &mut Self {
|
||||
self.href.set_value(href);
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_html(&mut self, html: L10n) -> &mut Self {
|
||||
self.html.set_value(html);
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_target(&mut self, target: ButtonTarget) -> &mut Self {
|
||||
self.target = target;
|
||||
self
|
||||
}
|
||||
|
||||
// Button GETTERS.
|
||||
|
||||
pub fn style(&self) -> &StyleBase {
|
||||
&self.style
|
||||
}
|
||||
|
||||
pub fn font_size(&self) -> &FontSize {
|
||||
&self.font_size
|
||||
}
|
||||
|
||||
pub fn left_icon(&self) -> &OptionComponent<Icon> {
|
||||
&self.left_icon
|
||||
}
|
||||
|
||||
pub fn right_icon(&self) -> &OptionComponent<Icon> {
|
||||
&self.right_icon
|
||||
}
|
||||
|
||||
pub fn href(&self) -> &OptionString {
|
||||
&self.href
|
||||
}
|
||||
|
||||
pub fn html(&self) -> &OptionTranslated {
|
||||
&self.html
|
||||
}
|
||||
|
||||
pub fn target(&self) -> &ButtonTarget {
|
||||
&self.target
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
pub struct Error403;
|
||||
|
||||
impl ComponentTrait for Error403 {
|
||||
fn new() -> Self {
|
||||
Error403
|
||||
}
|
||||
|
||||
fn prepare_component(&self, _cx: &mut Context) -> PrepareMarkup {
|
||||
PrepareMarkup::With(html! {
|
||||
div {
|
||||
h1 { ("FORBIDDEN ACCESS") }
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
pub struct Error404;
|
||||
|
||||
impl ComponentTrait for Error404 {
|
||||
fn new() -> Self {
|
||||
Error404
|
||||
}
|
||||
|
||||
fn prepare_component(&self, _cx: &mut Context) -> PrepareMarkup {
|
||||
PrepareMarkup::With(html! {
|
||||
div {
|
||||
h1 { ("RESOURCE NOT FOUND") }
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,311 +0,0 @@
|
|||
mod container;
|
||||
pub use container::Container;
|
||||
|
||||
mod item;
|
||||
pub use item::Item;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
// *************************************************************************************************
|
||||
|
||||
#[derive(AutoDefault)]
|
||||
pub enum Direction {
|
||||
#[default]
|
||||
Default,
|
||||
Row(BreakPoint),
|
||||
RowReverse(BreakPoint),
|
||||
Column(BreakPoint),
|
||||
ColumnReverse(BreakPoint),
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
impl fmt::Display for Direction {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Direction::Default => write!(f, "flex__row {}", BreakPoint::default()),
|
||||
Direction::Row(bp) => write!(f, "flex__row {bp}"),
|
||||
Direction::RowReverse(bp) => write!(f, "flex__row flex__reverse {bp}"),
|
||||
Direction::Column(bp) => write!(f, "flex__col {bp}"),
|
||||
Direction::ColumnReverse(bp) => write!(f, "flex__col flex__reverse {bp}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// *************************************************************************************************
|
||||
|
||||
#[derive(AutoDefault)]
|
||||
pub enum Wrap {
|
||||
#[default]
|
||||
Default,
|
||||
NoWrap,
|
||||
Wrap(ContentAlign),
|
||||
WrapReverse(ContentAlign),
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
impl fmt::Display for Wrap {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Wrap::Default => write!(f, ""),
|
||||
Wrap::NoWrap => write!(f, "flex__nowrap"),
|
||||
Wrap::Wrap(a) => write!(f, "flex__wrap {a}"),
|
||||
Wrap::WrapReverse(a) => write!(f, "flex__wrap-reverse {a}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// *************************************************************************************************
|
||||
|
||||
#[derive(AutoDefault)]
|
||||
pub enum ContentAlign {
|
||||
#[default]
|
||||
Default,
|
||||
Start,
|
||||
End,
|
||||
Center,
|
||||
Stretch,
|
||||
SpaceBetween,
|
||||
SpaceAround,
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
impl fmt::Display for ContentAlign {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
ContentAlign::Default => write!(f, ""),
|
||||
ContentAlign::Start => write!(f, "flex__align-start"),
|
||||
ContentAlign::End => write!(f, "flex__align-end"),
|
||||
ContentAlign::Center => write!(f, "flex__align-center"),
|
||||
ContentAlign::Stretch => write!(f, "flex__align-stretch"),
|
||||
ContentAlign::SpaceBetween => write!(f, "flex__align-space-between"),
|
||||
ContentAlign::SpaceAround => write!(f, "flex__align-space-around"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// *************************************************************************************************
|
||||
|
||||
#[derive(AutoDefault)]
|
||||
pub enum Justify {
|
||||
#[default]
|
||||
Default,
|
||||
Start,
|
||||
End,
|
||||
Center,
|
||||
SpaceBetween,
|
||||
SpaceAround,
|
||||
SpaceEvenly,
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
impl fmt::Display for Justify {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Justify::Default => write!(f, ""),
|
||||
Justify::Start => write!(f, "flex__justify-start"),
|
||||
Justify::End => write!(f, "flex__justify-end"),
|
||||
Justify::Center => write!(f, "flex__justify-center"),
|
||||
Justify::SpaceBetween => write!(f, "flex__justify-space-between"),
|
||||
Justify::SpaceAround => write!(f, "flex__justify-space-around"),
|
||||
Justify::SpaceEvenly => write!(f, "flex__justify-space-evenly"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// *************************************************************************************************
|
||||
|
||||
#[derive(AutoDefault)]
|
||||
pub enum Align {
|
||||
#[default]
|
||||
Default,
|
||||
Start,
|
||||
End,
|
||||
Center,
|
||||
Stretch,
|
||||
Baseline,
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
impl fmt::Display for Align {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Align::Default => write!(f, ""),
|
||||
Align::Start => write!(f, "flex__start"),
|
||||
Align::End => write!(f, "flex__end"),
|
||||
Align::Center => write!(f, "flex__center"),
|
||||
Align::Stretch => write!(f, "flex__stretch"),
|
||||
Align::Baseline => write!(f, "flex__baseline"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// *************************************************************************************************
|
||||
|
||||
#[derive(AutoDefault)]
|
||||
pub enum Gap {
|
||||
#[default]
|
||||
Default,
|
||||
Row(unit::Value),
|
||||
Column(unit::Value),
|
||||
Distinct(unit::Value, unit::Value),
|
||||
Both(unit::Value),
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
impl fmt::Display for Gap {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Gap::Default => write!(f, ""),
|
||||
Gap::Row(r) => write!(f, "row-gap: {r};"),
|
||||
Gap::Column(c) => write!(f, "column-gap: {c};"),
|
||||
Gap::Distinct(r, c) => write!(f, "gap: {r} {c};"),
|
||||
Gap::Both(v) => write!(f, "gap: {v};"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// *************************************************************************************************
|
||||
|
||||
#[derive(AutoDefault)]
|
||||
pub enum Grow {
|
||||
#[default]
|
||||
Default,
|
||||
Is1,
|
||||
Is2,
|
||||
Is3,
|
||||
Is4,
|
||||
Is5,
|
||||
Is6,
|
||||
Is7,
|
||||
Is8,
|
||||
Is9,
|
||||
}
|
||||
|
||||
impl fmt::Display for Grow {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Grow::Default => write!(f, ""),
|
||||
Grow::Is1 => write!(f, "flex__grow-1"),
|
||||
Grow::Is2 => write!(f, "flex__grow-2"),
|
||||
Grow::Is3 => write!(f, "flex__grow-3"),
|
||||
Grow::Is4 => write!(f, "flex__grow-4"),
|
||||
Grow::Is5 => write!(f, "flex__grow-5"),
|
||||
Grow::Is6 => write!(f, "flex__grow-6"),
|
||||
Grow::Is7 => write!(f, "flex__grow-7"),
|
||||
Grow::Is8 => write!(f, "flex__grow-8"),
|
||||
Grow::Is9 => write!(f, "flex__grow-9"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// *************************************************************************************************
|
||||
|
||||
#[derive(AutoDefault)]
|
||||
pub enum Shrink {
|
||||
#[default]
|
||||
Default,
|
||||
Is1,
|
||||
Is2,
|
||||
Is3,
|
||||
Is4,
|
||||
Is5,
|
||||
Is6,
|
||||
Is7,
|
||||
Is8,
|
||||
Is9,
|
||||
}
|
||||
|
||||
impl fmt::Display for Shrink {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Shrink::Default => write!(f, ""),
|
||||
Shrink::Is1 => write!(f, "flex__shrink-1"),
|
||||
Shrink::Is2 => write!(f, "flex__shrink-2"),
|
||||
Shrink::Is3 => write!(f, "flex__shrink-3"),
|
||||
Shrink::Is4 => write!(f, "flex__shrink-4"),
|
||||
Shrink::Is5 => write!(f, "flex__shrink-5"),
|
||||
Shrink::Is6 => write!(f, "flex__shrink-6"),
|
||||
Shrink::Is7 => write!(f, "flex__shrink-7"),
|
||||
Shrink::Is8 => write!(f, "flex__shrink-8"),
|
||||
Shrink::Is9 => write!(f, "flex__shrink-9"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// *************************************************************************************************
|
||||
|
||||
#[derive(AutoDefault)]
|
||||
pub enum Size {
|
||||
#[default]
|
||||
Default,
|
||||
Percent10,
|
||||
Percent20,
|
||||
Percent25,
|
||||
Percent33,
|
||||
Percent40,
|
||||
Percent50,
|
||||
Percent60,
|
||||
Percent66,
|
||||
Percent75,
|
||||
Percent80,
|
||||
Percent90,
|
||||
}
|
||||
|
||||
impl fmt::Display for Size {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Size::Default => write!(f, ""),
|
||||
Size::Percent10 => write!(f, "flex__size-10"),
|
||||
Size::Percent20 => write!(f, "flex__size-20"),
|
||||
Size::Percent25 => write!(f, "flex__size-25"),
|
||||
Size::Percent33 => write!(f, "flex__size-33"),
|
||||
Size::Percent40 => write!(f, "flex__size-40"),
|
||||
Size::Percent50 => write!(f, "flex__size-50"),
|
||||
Size::Percent60 => write!(f, "flex__size-60"),
|
||||
Size::Percent66 => write!(f, "flex__size-66"),
|
||||
Size::Percent75 => write!(f, "flex__size-75"),
|
||||
Size::Percent80 => write!(f, "flex__size-80"),
|
||||
Size::Percent90 => write!(f, "flex__size-90"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// *************************************************************************************************
|
||||
|
||||
#[derive(AutoDefault)]
|
||||
pub enum Offset {
|
||||
#[default]
|
||||
Default,
|
||||
Offset10,
|
||||
Offset20,
|
||||
Offset25,
|
||||
Offset33,
|
||||
Offset40,
|
||||
Offset50,
|
||||
Offset60,
|
||||
Offset66,
|
||||
Offset75,
|
||||
Offset80,
|
||||
Offset90,
|
||||
}
|
||||
|
||||
impl fmt::Display for Offset {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Offset::Default => write!(f, ""),
|
||||
Offset::Offset10 => write!(f, "flex__offset-10"),
|
||||
Offset::Offset20 => write!(f, "flex__offset-20"),
|
||||
Offset::Offset25 => write!(f, "flex__offset-25"),
|
||||
Offset::Offset33 => write!(f, "flex__offset-33"),
|
||||
Offset::Offset40 => write!(f, "flex__offset-40"),
|
||||
Offset::Offset50 => write!(f, "flex__offset-50"),
|
||||
Offset::Offset60 => write!(f, "flex__offset-60"),
|
||||
Offset::Offset66 => write!(f, "flex__offset-66"),
|
||||
Offset::Offset75 => write!(f, "flex__offset-75"),
|
||||
Offset::Offset80 => write!(f, "flex__offset-80"),
|
||||
Offset::Offset90 => write!(f, "flex__offset-90"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,212 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
#[derive(AutoDefault)]
|
||||
pub enum ContainerType {
|
||||
#[default]
|
||||
Default,
|
||||
Header,
|
||||
Main,
|
||||
Section,
|
||||
Article,
|
||||
Footer,
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[derive(AutoDefault, ComponentClasses)]
|
||||
pub struct Container {
|
||||
id : OptionId,
|
||||
classes : OptionClasses,
|
||||
container_type: ContainerType,
|
||||
direction : flex::Direction,
|
||||
flex_wrap : flex::Wrap,
|
||||
flex_justify : flex::Justify,
|
||||
flex_align : flex::Align,
|
||||
flex_gap : flex::Gap,
|
||||
items : MixedComponents,
|
||||
}
|
||||
|
||||
impl ComponentTrait for Container {
|
||||
fn new() -> Self {
|
||||
Container::default()
|
||||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.id.get()
|
||||
}
|
||||
|
||||
fn setup_before_prepare(&mut self, cx: &mut Context) {
|
||||
self.set_classes(
|
||||
ClassesOp::Prepend,
|
||||
[
|
||||
"flex__container".to_string(),
|
||||
self.direction().to_string(),
|
||||
self.wrap().to_string(),
|
||||
self.justify().to_string(),
|
||||
self.align().to_string(),
|
||||
]
|
||||
.join(" "),
|
||||
);
|
||||
|
||||
cx.set_param::<bool>(PARAM_BASE_INCLUDE_FLEX_ASSETS, &true);
|
||||
}
|
||||
|
||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
||||
let output = self.items().render(cx);
|
||||
if output.is_empty() {
|
||||
return PrepareMarkup::None;
|
||||
}
|
||||
|
||||
let gap = match self.gap() {
|
||||
flex::Gap::Default => None,
|
||||
_ => Some(self.gap().to_string()),
|
||||
};
|
||||
match self.container_type() {
|
||||
ContainerType::Default => PrepareMarkup::With(html! {
|
||||
div id=[self.id()] class=[self.classes().get()] style=[gap] {
|
||||
(output)
|
||||
}
|
||||
}),
|
||||
ContainerType::Header => PrepareMarkup::With(html! {
|
||||
header id=[self.id()] class=[self.classes().get()] style=[gap] {
|
||||
(output)
|
||||
}
|
||||
}),
|
||||
ContainerType::Main => PrepareMarkup::With(html! {
|
||||
main id=[self.id()] class=[self.classes().get()] style=[gap] {
|
||||
(output)
|
||||
}
|
||||
}),
|
||||
ContainerType::Section => PrepareMarkup::With(html! {
|
||||
section id=[self.id()] class=[self.classes().get()] style=[gap] {
|
||||
(output)
|
||||
}
|
||||
}),
|
||||
ContainerType::Article => PrepareMarkup::With(html! {
|
||||
article id=[self.id()] class=[self.classes().get()] style=[gap] {
|
||||
(output)
|
||||
}
|
||||
}),
|
||||
ContainerType::Footer => PrepareMarkup::With(html! {
|
||||
footer id=[self.id()] class=[self.classes().get()] style=[gap] {
|
||||
(output)
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Container {
|
||||
pub fn header() -> Self {
|
||||
Container {
|
||||
container_type: ContainerType::Header,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main() -> Self {
|
||||
Container {
|
||||
container_type: ContainerType::Main,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn section() -> Self {
|
||||
Container {
|
||||
container_type: ContainerType::Section,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn article() -> Self {
|
||||
Container {
|
||||
container_type: ContainerType::Article,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn footer() -> Self {
|
||||
Container {
|
||||
container_type: ContainerType::Footer,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
// Container BUILDER.
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_id(&mut self, id: impl Into<String>) -> &mut Self {
|
||||
self.id.set_value(id);
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_direction(&mut self, direction: flex::Direction) -> &mut Self {
|
||||
self.direction = direction;
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_wrap(&mut self, wrap: flex::Wrap) -> &mut Self {
|
||||
self.flex_wrap = wrap;
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_justify(&mut self, justify: flex::Justify) -> &mut Self {
|
||||
self.flex_justify = justify;
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_align(&mut self, align: flex::Align) -> &mut Self {
|
||||
self.flex_align = align;
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_gap(&mut self, gap: flex::Gap) -> &mut Self {
|
||||
self.flex_gap = gap;
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_items(&mut self, op: TypedOp<flex::Item>) -> &mut Self {
|
||||
self.items.set_typed(op);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_item(mut self, item: flex::Item) -> Self {
|
||||
self.items.set_value(AnyOp::Add(AnyComponent::with(item)));
|
||||
self
|
||||
}
|
||||
|
||||
// Container GETTERS.
|
||||
|
||||
pub fn container_type(&self) -> &ContainerType {
|
||||
&self.container_type
|
||||
}
|
||||
|
||||
pub fn direction(&self) -> &flex::Direction {
|
||||
&self.direction
|
||||
}
|
||||
|
||||
pub fn wrap(&self) -> &flex::Wrap {
|
||||
&self.flex_wrap
|
||||
}
|
||||
|
||||
pub fn justify(&self) -> &flex::Justify {
|
||||
&self.flex_justify
|
||||
}
|
||||
|
||||
pub fn align(&self) -> &flex::Align {
|
||||
&self.flex_align
|
||||
}
|
||||
|
||||
pub fn gap(&self) -> &flex::Gap {
|
||||
&self.flex_gap
|
||||
}
|
||||
|
||||
pub fn items(&self) -> &MixedComponents {
|
||||
&self.items
|
||||
}
|
||||
}
|
||||
|
|
@ -1,200 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
#[derive(AutoDefault)]
|
||||
pub enum ItemType {
|
||||
#[default]
|
||||
Default,
|
||||
Region,
|
||||
Wrapper,
|
||||
Bundle,
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[derive(AutoDefault, ComponentClasses)]
|
||||
pub struct Item {
|
||||
id : OptionId,
|
||||
classes : OptionClasses,
|
||||
item_type : ItemType,
|
||||
flex_grow : flex::Grow,
|
||||
flex_shrink: flex::Shrink,
|
||||
flex_size : flex::Size,
|
||||
flex_offset: flex::Offset,
|
||||
flex_align : flex::Align,
|
||||
mixed : MixedComponents,
|
||||
}
|
||||
|
||||
impl ComponentTrait for Item {
|
||||
fn new() -> Self {
|
||||
Item::default()
|
||||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.id.get()
|
||||
}
|
||||
|
||||
fn setup_before_prepare(&mut self, _cx: &mut Context) {
|
||||
self.set_classes(
|
||||
ClassesOp::Prepend,
|
||||
[
|
||||
"flex__item".to_string(),
|
||||
self.grow().to_string(),
|
||||
self.shrink().to_string(),
|
||||
self.size().to_string(),
|
||||
self.offset().to_string(),
|
||||
self.align().to_string(),
|
||||
]
|
||||
.join(" "),
|
||||
);
|
||||
}
|
||||
|
||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
||||
let (output, region) = match self.item_type() {
|
||||
ItemType::Region => (
|
||||
self.components().render(cx),
|
||||
if let Some(id) = self.id() {
|
||||
cx.prepare_region(id)
|
||||
} else {
|
||||
Markup::default()
|
||||
},
|
||||
),
|
||||
_ => (self.components().render(cx), Markup::default()),
|
||||
};
|
||||
if output.is_empty() && region.is_empty() {
|
||||
return PrepareMarkup::None;
|
||||
}
|
||||
match self.item_type() {
|
||||
ItemType::Default => PrepareMarkup::With(html! {
|
||||
div id=[self.id()] class=[self.classes().get()] {
|
||||
div class="flex__content" {
|
||||
(output)
|
||||
}
|
||||
}
|
||||
}),
|
||||
ItemType::Region => PrepareMarkup::With(html! {
|
||||
div id=[self.id()] class=[self.classes().get()] {
|
||||
div class="flex__content flex__region" {
|
||||
(region)
|
||||
(output)
|
||||
}
|
||||
}
|
||||
}),
|
||||
ItemType::Wrapper => PrepareMarkup::With(html! {
|
||||
div id=[self.id()] class=[self.classes().get()] {
|
||||
(output)
|
||||
}
|
||||
}),
|
||||
ItemType::Bundle => PrepareMarkup::With(html! {
|
||||
(output)
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Item {
|
||||
pub fn region() -> Self {
|
||||
Item {
|
||||
item_type: ItemType::Region,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wrapper() -> Self {
|
||||
Item {
|
||||
item_type: ItemType::Wrapper,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bundle() -> Self {
|
||||
Item {
|
||||
item_type: ItemType::Bundle,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with(component: impl ComponentTrait) -> Self {
|
||||
Item::default().add_component(component)
|
||||
}
|
||||
|
||||
// Item BUILDER.
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_id(&mut self, id: impl Into<String>) -> &mut Self {
|
||||
self.id.set_value(id);
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_grow(&mut self, grow: flex::Grow) -> &mut Self {
|
||||
self.flex_grow = grow;
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_shrink(&mut self, shrink: flex::Shrink) -> &mut Self {
|
||||
self.flex_shrink = shrink;
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
// Ensures the item occupies the exact specified width, neither growing nor shrinking,
|
||||
// regardless of the available space in the container or the size of other items.
|
||||
pub fn set_size(&mut self, size: flex::Size) -> &mut Self {
|
||||
self.flex_size = size;
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_offset(&mut self, offset: flex::Offset) -> &mut Self {
|
||||
self.flex_offset = offset;
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_align(&mut self, align: flex::Align) -> &mut Self {
|
||||
self.flex_align = align;
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_components(&mut self, op: AnyOp) -> &mut Self {
|
||||
self.mixed.set_value(op);
|
||||
self
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub fn add_component(mut self, component: impl ComponentTrait) -> Self {
|
||||
self.mixed.set_value(AnyOp::Add(AnyComponent::with(component)));
|
||||
self
|
||||
}
|
||||
|
||||
// Item GETTERS.
|
||||
|
||||
pub fn item_type(&self) -> &ItemType {
|
||||
&self.item_type
|
||||
}
|
||||
|
||||
pub fn grow(&self) -> &flex::Grow {
|
||||
&self.flex_grow
|
||||
}
|
||||
|
||||
pub fn shrink(&self) -> &flex::Shrink {
|
||||
&self.flex_shrink
|
||||
}
|
||||
|
||||
pub fn size(&self) -> &flex::Size {
|
||||
&self.flex_size
|
||||
}
|
||||
|
||||
pub fn offset(&self) -> &flex::Offset {
|
||||
&self.flex_offset
|
||||
}
|
||||
|
||||
pub fn align(&self) -> &flex::Align {
|
||||
&self.flex_align
|
||||
}
|
||||
|
||||
pub fn components(&self) -> &MixedComponents {
|
||||
&self.mixed
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
mod form_main;
|
||||
pub use form_main::{Form, FormMethod};
|
||||
|
||||
mod input;
|
||||
pub use input::{Input, InputType};
|
||||
|
||||
mod hidden;
|
||||
pub use hidden::Hidden;
|
||||
|
||||
mod date;
|
||||
pub use date::Date;
|
||||
|
||||
mod action_button;
|
||||
pub use action_button::{ActionButton, ActionButtonType};
|
||||
|
|
@ -1,182 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
#[derive(AutoDefault)]
|
||||
pub enum ActionButtonType {
|
||||
#[default]
|
||||
Submit,
|
||||
Reset,
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
impl fmt::Display for ActionButtonType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
ActionButtonType::Submit => write!(f, "submit"),
|
||||
ActionButtonType::Reset => write!(f, "reset"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[derive(AutoDefault, ComponentClasses)]
|
||||
pub struct ActionButton {
|
||||
classes : OptionClasses,
|
||||
button_type: ActionButtonType,
|
||||
style : StyleBase,
|
||||
font_size : FontSize,
|
||||
left_icon : OptionComponent<Icon>,
|
||||
right_icon : OptionComponent<Icon>,
|
||||
name : OptionString,
|
||||
value : OptionTranslated,
|
||||
autofocus : OptionString,
|
||||
disabled : OptionString,
|
||||
}
|
||||
|
||||
impl ComponentTrait for ActionButton {
|
||||
fn new() -> Self {
|
||||
ActionButton::submit()
|
||||
}
|
||||
|
||||
fn setup_before_prepare(&mut self, _cx: &mut Context) {
|
||||
self.set_classes(
|
||||
ClassesOp::Prepend,
|
||||
[
|
||||
"button__tap".to_string(),
|
||||
self.style().to_string(),
|
||||
self.font_size().to_string(),
|
||||
]
|
||||
.join(" "),
|
||||
);
|
||||
}
|
||||
|
||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
||||
let id = self.name().get().map(|name| concat_string!("edit-", name));
|
||||
PrepareMarkup::With(html! {
|
||||
button
|
||||
type=(self.button_type().to_string())
|
||||
id=[id]
|
||||
class=[self.classes().get()]
|
||||
name=[self.name().get()]
|
||||
value=[self.value().using(cx.langid())]
|
||||
autofocus=[self.autofocus().get()]
|
||||
disabled=[self.disabled().get()]
|
||||
{
|
||||
(self.left_icon().render(cx))
|
||||
span { (self.value().escaped(cx.langid())) }
|
||||
(self.right_icon().render(cx))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ActionButton {
|
||||
pub fn submit() -> Self {
|
||||
ActionButton {
|
||||
button_type: ActionButtonType::Submit,
|
||||
style: StyleBase::Default,
|
||||
value: OptionTranslated::new(L10n::l("button_submit")),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset() -> Self {
|
||||
ActionButton {
|
||||
button_type: ActionButtonType::Reset,
|
||||
style: StyleBase::Info,
|
||||
value: OptionTranslated::new(L10n::l("button_reset")),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
// Button BUILDER.
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_style(&mut self, style: StyleBase) -> &mut Self {
|
||||
self.style = style;
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_font_size(&mut self, font_size: FontSize) -> &mut Self {
|
||||
self.font_size = font_size;
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_left_icon(&mut self, icon: Option<Icon>) -> &mut Self {
|
||||
self.left_icon.set_value(icon);
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_right_icon(&mut self, icon: Option<Icon>) -> &mut Self {
|
||||
self.right_icon.set_value(icon);
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_name(&mut self, name: &str) -> &mut Self {
|
||||
self.name.set_value(name);
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_value(&mut self, value: L10n) -> &mut Self {
|
||||
self.value.set_value(value);
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_autofocus(&mut self, toggle: bool) -> &mut Self {
|
||||
self.autofocus
|
||||
.set_value(if toggle { "autofocus" } else { "" });
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_disabled(&mut self, toggle: bool) -> &mut Self {
|
||||
self.disabled
|
||||
.set_value(if toggle { "disabled" } else { "" });
|
||||
self
|
||||
}
|
||||
|
||||
// Button GETTERS.
|
||||
|
||||
pub fn button_type(&self) -> &ActionButtonType {
|
||||
&self.button_type
|
||||
}
|
||||
|
||||
pub fn style(&self) -> &StyleBase {
|
||||
&self.style
|
||||
}
|
||||
|
||||
pub fn font_size(&self) -> &FontSize {
|
||||
&self.font_size
|
||||
}
|
||||
|
||||
pub fn left_icon(&self) -> &OptionComponent<Icon> {
|
||||
&self.left_icon
|
||||
}
|
||||
|
||||
pub fn right_icon(&self) -> &OptionComponent<Icon> {
|
||||
&self.right_icon
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &OptionString {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn value(&self) -> &OptionTranslated {
|
||||
&self.value
|
||||
}
|
||||
|
||||
pub fn autofocus(&self) -> &OptionString {
|
||||
&self.autofocus
|
||||
}
|
||||
|
||||
pub fn disabled(&self) -> &OptionString {
|
||||
&self.disabled
|
||||
}
|
||||
}
|
||||
|
|
@ -1,166 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[derive(AutoDefault, ComponentClasses)]
|
||||
pub struct Date {
|
||||
classes : OptionClasses,
|
||||
name : OptionString,
|
||||
value : OptionString,
|
||||
label : OptionString,
|
||||
placeholder : OptionString,
|
||||
autofocus : OptionString,
|
||||
autocomplete: OptionString,
|
||||
disabled : OptionString,
|
||||
readonly : OptionString,
|
||||
required : OptionString,
|
||||
help_text : OptionString,
|
||||
}
|
||||
|
||||
impl ComponentTrait for Date {
|
||||
fn new() -> Self {
|
||||
Date::default().with_classes(ClassesOp::Add, "form-item form-type-date")
|
||||
}
|
||||
|
||||
fn prepare_component(&self, _cx: &mut Context) -> PrepareMarkup {
|
||||
let id = self.name().get().map(|name| concat_string!("edit-", name));
|
||||
PrepareMarkup::With(html! {
|
||||
div class=[self.classes().get()] {
|
||||
@if let Some(label) = self.label().get() {
|
||||
label class="form-label" for=[&id] {
|
||||
(label) " "
|
||||
@if self.required().get().is_some() {
|
||||
span
|
||||
class="form-required"
|
||||
title="Este campo es obligatorio." { "*" } " "
|
||||
}
|
||||
}
|
||||
}
|
||||
input
|
||||
type="date"
|
||||
id=[id]
|
||||
class="form-control"
|
||||
name=[self.name().get()]
|
||||
value=[self.value().get()]
|
||||
placeholder=[self.placeholder().get()]
|
||||
autofocus=[self.autofocus().get()]
|
||||
autocomplete=[self.autocomplete().get()]
|
||||
readonly=[self.readonly().get()]
|
||||
required=[self.required().get()]
|
||||
disabled=[self.disabled().get()] {}
|
||||
@if let Some(help_text) = self.help_text().get() {
|
||||
div class="form-text" { (help_text) }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Date {
|
||||
// Date BUILDER.
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_name(&mut self, name: &str) -> &mut Self {
|
||||
self.name.set_value(name);
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_value(&mut self, value: &str) -> &mut Self {
|
||||
self.value.set_value(value);
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_label(&mut self, label: &str) -> &mut Self {
|
||||
self.label.set_value(label);
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_placeholder(&mut self, placeholder: &str) -> &mut Self {
|
||||
self.placeholder.set_value(placeholder);
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_autofocus(&mut self, toggle: bool) -> &mut Self {
|
||||
self.autofocus
|
||||
.set_value(if toggle { "autofocus" } else { "" });
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_autocomplete(&mut self, toggle: bool) -> &mut Self {
|
||||
self.autocomplete.set_value(if toggle { "" } else { "off" });
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_disabled(&mut self, toggle: bool) -> &mut Self {
|
||||
self.disabled
|
||||
.set_value(if toggle { "disabled" } else { "" });
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_readonly(&mut self, toggle: bool) -> &mut Self {
|
||||
self.readonly
|
||||
.set_value(if toggle { "readonly" } else { "" });
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_required(&mut self, toggle: bool) -> &mut Self {
|
||||
self.required
|
||||
.set_value(if toggle { "required" } else { "" });
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_help_text(&mut self, help_text: &str) -> &mut Self {
|
||||
self.help_text.set_value(help_text);
|
||||
self
|
||||
}
|
||||
|
||||
// Date GETTERS.
|
||||
|
||||
pub fn name(&self) -> &OptionString {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn value(&self) -> &OptionString {
|
||||
&self.value
|
||||
}
|
||||
|
||||
pub fn label(&self) -> &OptionString {
|
||||
&self.label
|
||||
}
|
||||
|
||||
pub fn placeholder(&self) -> &OptionString {
|
||||
&self.placeholder
|
||||
}
|
||||
|
||||
pub fn autofocus(&self) -> &OptionString {
|
||||
&self.autofocus
|
||||
}
|
||||
|
||||
pub fn autocomplete(&self) -> &OptionString {
|
||||
&self.autocomplete
|
||||
}
|
||||
|
||||
pub fn disabled(&self) -> &OptionString {
|
||||
&self.disabled
|
||||
}
|
||||
|
||||
pub fn readonly(&self) -> &OptionString {
|
||||
&self.readonly
|
||||
}
|
||||
|
||||
pub fn required(&self) -> &OptionString {
|
||||
&self.required
|
||||
}
|
||||
|
||||
pub fn help_text(&self) -> &OptionString {
|
||||
&self.help_text
|
||||
}
|
||||
}
|
||||
|
|
@ -1,107 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
#[derive(AutoDefault)]
|
||||
pub enum FormMethod {
|
||||
#[default]
|
||||
Post,
|
||||
Get,
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[derive(AutoDefault, ComponentClasses)]
|
||||
pub struct Form {
|
||||
id : OptionId,
|
||||
classes: OptionClasses,
|
||||
action : OptionString,
|
||||
charset: OptionString,
|
||||
method : FormMethod,
|
||||
mixed : MixedComponents,
|
||||
}
|
||||
|
||||
impl ComponentTrait for Form {
|
||||
fn new() -> Self {
|
||||
Form::default()
|
||||
.with_classes(ClassesOp::Add, "form")
|
||||
.with_charset("UTF-8")
|
||||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.id.get()
|
||||
}
|
||||
|
||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
||||
let method = match self.method() {
|
||||
FormMethod::Post => Some("post".to_owned()),
|
||||
FormMethod::Get => None,
|
||||
};
|
||||
PrepareMarkup::With(html! {
|
||||
form
|
||||
id=[self.id()]
|
||||
class=[self.classes().get()]
|
||||
action=[self.action().get()]
|
||||
method=[method]
|
||||
accept-charset=[self.charset().get()]
|
||||
{
|
||||
div { (self.elements().render(cx)) }
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Form {
|
||||
// Form BUILDER.
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_id(&mut self, id: impl Into<String>) -> &mut Self {
|
||||
self.id.set_value(id);
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_action(&mut self, action: &str) -> &mut Self {
|
||||
self.action.set_value(action);
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_charset(&mut self, charset: &str) -> &mut Self {
|
||||
self.charset.set_value(charset);
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_method(&mut self, method: FormMethod) -> &mut Self {
|
||||
self.method = method;
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_elements(&mut self, op: AnyOp) -> &mut Self {
|
||||
self.mixed.set_value(op);
|
||||
self
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub fn add_element(mut self, element: impl ComponentTrait) -> Self {
|
||||
self.mixed.set_value(AnyOp::Add(AnyComponent::with(element)));
|
||||
self
|
||||
}
|
||||
|
||||
// Form GETTERS.
|
||||
|
||||
pub fn action(&self) -> &OptionString {
|
||||
&self.action
|
||||
}
|
||||
|
||||
pub fn charset(&self) -> &OptionString {
|
||||
&self.charset
|
||||
}
|
||||
|
||||
pub fn method(&self) -> &FormMethod {
|
||||
&self.method
|
||||
}
|
||||
|
||||
pub fn elements(&self) -> &MixedComponents {
|
||||
&self.mixed
|
||||
}
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[derive(AutoDefault)]
|
||||
pub struct Hidden {
|
||||
name : OptionName,
|
||||
value : OptionString,
|
||||
}
|
||||
|
||||
impl ComponentTrait for Hidden {
|
||||
fn new() -> Self {
|
||||
Hidden::default()
|
||||
}
|
||||
|
||||
fn prepare_component(&self, _cx: &mut Context) -> PrepareMarkup {
|
||||
let id = self.name().get().map(|name| concat_string!("value-", name));
|
||||
PrepareMarkup::With(html! {
|
||||
input type="hidden" id=[id] name=[self.name().get()] value=[self.value().get()] {}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Hidden {
|
||||
pub fn set(name: &str, value: &str) -> Self {
|
||||
Hidden::default().with_name(name).with_value(value)
|
||||
}
|
||||
|
||||
// Hidden BUILDER.
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_name(&mut self, name: &str) -> &mut Self {
|
||||
self.name.set_value(name);
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_value(&mut self, value: &str) -> &mut Self {
|
||||
self.value.set_value(value);
|
||||
self
|
||||
}
|
||||
|
||||
// Hidden GETTERS.
|
||||
|
||||
pub fn name(&self) -> &OptionName {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn value(&self) -> &OptionString {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
|
|
@ -1,283 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
#[derive(AutoDefault)]
|
||||
pub enum InputType {
|
||||
#[default]
|
||||
Textfield,
|
||||
Password,
|
||||
Search,
|
||||
Email,
|
||||
Telephone,
|
||||
Url,
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[derive(AutoDefault, ComponentClasses)]
|
||||
pub struct Input {
|
||||
classes : OptionClasses,
|
||||
input_type : InputType,
|
||||
name : OptionName,
|
||||
value : OptionString,
|
||||
label : OptionTranslated,
|
||||
size : Option<u16>,
|
||||
minlength : Option<u16>,
|
||||
maxlength : Option<u16>,
|
||||
placeholder : OptionString,
|
||||
autofocus : OptionString,
|
||||
autocomplete: OptionString,
|
||||
disabled : OptionString,
|
||||
readonly : OptionString,
|
||||
required : OptionString,
|
||||
help_text : OptionTranslated,
|
||||
}
|
||||
|
||||
impl ComponentTrait for Input {
|
||||
fn new() -> Self {
|
||||
Input::default()
|
||||
.with_classes(ClassesOp::Add, "form-item form-type-textfield")
|
||||
.with_size(Some(60))
|
||||
.with_maxlength(Some(128))
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
||||
let type_input = match self.input_type() {
|
||||
InputType::Textfield => "text",
|
||||
InputType::Password => "password",
|
||||
InputType::Search => "search",
|
||||
InputType::Email => "email",
|
||||
InputType::Telephone => "tel",
|
||||
InputType::Url => "url",
|
||||
};
|
||||
let id = self.name().get().map(|name| concat_string!("edit-", name));
|
||||
PrepareMarkup::With(html! {
|
||||
div class=[self.classes().get()] {
|
||||
@if let Some(label) = self.label().using(cx.langid()) {
|
||||
label class="form-label" for=[&id] {
|
||||
(label) " "
|
||||
@if self.required().get().is_some() {
|
||||
span
|
||||
class="form-required"
|
||||
title="Este campo es obligatorio." { "*" } " "
|
||||
}
|
||||
}
|
||||
}
|
||||
input
|
||||
type=(type_input)
|
||||
id=[id]
|
||||
class="form-control"
|
||||
name=[self.name().get()]
|
||||
value=[self.value().get()]
|
||||
size=[self.size()]
|
||||
minlength=[self.minlength()]
|
||||
maxlength=[self.maxlength()]
|
||||
placeholder=[self.placeholder().get()]
|
||||
autofocus=[self.autofocus().get()]
|
||||
autocomplete=[self.autocomplete().get()]
|
||||
readonly=[self.readonly().get()]
|
||||
required=[self.required().get()]
|
||||
disabled=[self.disabled().get()] {}
|
||||
@if let Some(description) = self.help_text().using(cx.langid()) {
|
||||
div class="form-text" { (description) }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Input {
|
||||
pub fn textfield() -> Self {
|
||||
Input::default()
|
||||
}
|
||||
|
||||
pub fn password() -> Self {
|
||||
let mut input = Input::default().with_classes(
|
||||
ClassesOp::Replace("form-type-textfield".to_owned()),
|
||||
"form-type-password",
|
||||
);
|
||||
input.input_type = InputType::Password;
|
||||
input
|
||||
}
|
||||
|
||||
pub fn search() -> Self {
|
||||
let mut input = Input::default().with_classes(
|
||||
ClassesOp::Replace("form-type-textfield".to_owned()),
|
||||
"form-type-search",
|
||||
);
|
||||
input.input_type = InputType::Search;
|
||||
input
|
||||
}
|
||||
|
||||
pub fn email() -> Self {
|
||||
let mut input = Input::default().with_classes(
|
||||
ClassesOp::Replace("form-type-textfield".to_owned()),
|
||||
"form-type-email",
|
||||
);
|
||||
input.input_type = InputType::Email;
|
||||
input
|
||||
}
|
||||
|
||||
pub fn telephone() -> Self {
|
||||
let mut input = Input::default().with_classes(
|
||||
ClassesOp::Replace("form-type-textfield".to_owned()),
|
||||
"form-type-telephone",
|
||||
);
|
||||
input.input_type = InputType::Telephone;
|
||||
input
|
||||
}
|
||||
|
||||
pub fn url() -> Self {
|
||||
let mut input = Input::default().with_classes(
|
||||
ClassesOp::Replace("form-type-textfield".to_owned()),
|
||||
"form-type-url",
|
||||
);
|
||||
input.input_type = InputType::Url;
|
||||
input
|
||||
}
|
||||
|
||||
// Input BUILDER.
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_name(&mut self, name: &str) -> &mut Self {
|
||||
if let Some(previous) = self.name.get() {
|
||||
self.set_classes(ClassesOp::Remove, concat_string!("form-item-", previous));
|
||||
}
|
||||
self.set_classes(ClassesOp::Add, concat_string!("form-item-", name));
|
||||
self.name.set_value(name);
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_value(&mut self, value: &str) -> &mut Self {
|
||||
self.value.set_value(value);
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_label(&mut self, label: L10n) -> &mut Self {
|
||||
self.label.set_value(label);
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_size(&mut self, size: Option<u16>) -> &mut Self {
|
||||
self.size = size;
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_minlength(&mut self, minlength: Option<u16>) -> &mut Self {
|
||||
self.minlength = minlength;
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_maxlength(&mut self, maxlength: Option<u16>) -> &mut Self {
|
||||
self.maxlength = maxlength;
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_placeholder(&mut self, placeholder: &str) -> &mut Self {
|
||||
self.placeholder.set_value(placeholder);
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_autofocus(&mut self, toggle: bool) -> &mut Self {
|
||||
self.autofocus
|
||||
.set_value(if toggle { "autofocus" } else { "" });
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_autocomplete(&mut self, toggle: bool) -> &mut Self {
|
||||
self.autocomplete.set_value(if toggle { "" } else { "off" });
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_disabled(&mut self, toggle: bool) -> &mut Self {
|
||||
self.disabled
|
||||
.set_value(if toggle { "disabled" } else { "" });
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_readonly(&mut self, toggle: bool) -> &mut Self {
|
||||
self.readonly
|
||||
.set_value(if toggle { "readonly" } else { "" });
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_required(&mut self, toggle: bool) -> &mut Self {
|
||||
self.required
|
||||
.set_value(if toggle { "required" } else { "" });
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_help_text(&mut self, help_text: L10n) -> &mut Self {
|
||||
self.help_text.set_value(help_text);
|
||||
self
|
||||
}
|
||||
|
||||
// Input GETTERS.
|
||||
|
||||
pub fn input_type(&self) -> &InputType {
|
||||
&self.input_type
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &OptionName {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn value(&self) -> &OptionString {
|
||||
&self.value
|
||||
}
|
||||
|
||||
pub fn label(&self) -> &OptionTranslated {
|
||||
&self.label
|
||||
}
|
||||
|
||||
pub fn size(&self) -> Option<u16> {
|
||||
self.size
|
||||
}
|
||||
|
||||
pub fn minlength(&self) -> Option<u16> {
|
||||
self.minlength
|
||||
}
|
||||
|
||||
pub fn maxlength(&self) -> Option<u16> {
|
||||
self.maxlength
|
||||
}
|
||||
|
||||
pub fn placeholder(&self) -> &OptionString {
|
||||
&self.placeholder
|
||||
}
|
||||
|
||||
pub fn autofocus(&self) -> &OptionString {
|
||||
&self.autofocus
|
||||
}
|
||||
|
||||
pub fn autocomplete(&self) -> &OptionString {
|
||||
&self.autocomplete
|
||||
}
|
||||
|
||||
pub fn disabled(&self) -> &OptionString {
|
||||
&self.disabled
|
||||
}
|
||||
|
||||
pub fn readonly(&self) -> &OptionString {
|
||||
&self.readonly
|
||||
}
|
||||
|
||||
pub fn required(&self) -> &OptionString {
|
||||
&self.required
|
||||
}
|
||||
|
||||
pub fn help_text(&self) -> &OptionTranslated {
|
||||
&self.help_text
|
||||
}
|
||||
}
|
||||
|
|
@ -1,157 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
#[derive(AutoDefault)]
|
||||
pub enum HeadingType {
|
||||
#[default]
|
||||
H1,
|
||||
H2,
|
||||
H3,
|
||||
H4,
|
||||
H5,
|
||||
H6,
|
||||
}
|
||||
|
||||
#[derive(AutoDefault)]
|
||||
pub enum HeadingSize {
|
||||
ExtraLarge,
|
||||
XxLarge,
|
||||
XLarge,
|
||||
Large,
|
||||
Medium,
|
||||
#[default]
|
||||
Normal,
|
||||
Subtitle,
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
impl fmt::Display for HeadingSize {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
HeadingSize::ExtraLarge => write!(f, "heading__title-x3l"),
|
||||
HeadingSize::XxLarge => write!(f, "heading__title-x2l"),
|
||||
HeadingSize::XLarge => write!(f, "heading__title-xl"),
|
||||
HeadingSize::Large => write!(f, "heading__title-l"),
|
||||
HeadingSize::Medium => write!(f, "heading__title-m"),
|
||||
HeadingSize::Normal => write!(f, ""),
|
||||
HeadingSize::Subtitle => write!(f, "heading__subtitle"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[derive(AutoDefault, ComponentClasses)]
|
||||
pub struct Heading {
|
||||
id : OptionId,
|
||||
classes : OptionClasses,
|
||||
heading_type: HeadingType,
|
||||
size : HeadingSize,
|
||||
text : OptionTranslated,
|
||||
}
|
||||
|
||||
impl ComponentTrait for Heading {
|
||||
fn new() -> Self {
|
||||
Heading::default()
|
||||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.id.get()
|
||||
}
|
||||
|
||||
fn setup_before_prepare(&mut self, _cx: &mut Context) {
|
||||
self.set_classes(ClassesOp::Add, self.size().to_string());
|
||||
}
|
||||
|
||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
||||
let id = self.id();
|
||||
let classes = self.classes().get();
|
||||
let text = self.text().escaped(cx.langid());
|
||||
PrepareMarkup::With(html! { @match &self.heading_type() {
|
||||
HeadingType::H1 => h1 id=[id] class=[classes] { (text) },
|
||||
HeadingType::H2 => h2 id=[id] class=[classes] { (text) },
|
||||
HeadingType::H3 => h3 id=[id] class=[classes] { (text) },
|
||||
HeadingType::H4 => h4 id=[id] class=[classes] { (text) },
|
||||
HeadingType::H5 => h5 id=[id] class=[classes] { (text) },
|
||||
HeadingType::H6 => h6 id=[id] class=[classes] { (text) },
|
||||
}})
|
||||
}
|
||||
}
|
||||
|
||||
impl Heading {
|
||||
pub fn h1(text: L10n) -> Self {
|
||||
Heading::default()
|
||||
.with_heading_type(HeadingType::H1)
|
||||
.with_text(text)
|
||||
}
|
||||
|
||||
pub fn h2(text: L10n) -> Self {
|
||||
Heading::default()
|
||||
.with_heading_type(HeadingType::H2)
|
||||
.with_text(text)
|
||||
}
|
||||
|
||||
pub fn h3(text: L10n) -> Self {
|
||||
Heading::default()
|
||||
.with_heading_type(HeadingType::H3)
|
||||
.with_text(text)
|
||||
}
|
||||
|
||||
pub fn h4(text: L10n) -> Self {
|
||||
Heading::default()
|
||||
.with_heading_type(HeadingType::H4)
|
||||
.with_text(text)
|
||||
}
|
||||
|
||||
pub fn h5(text: L10n) -> Self {
|
||||
Heading::default()
|
||||
.with_heading_type(HeadingType::H5)
|
||||
.with_text(text)
|
||||
}
|
||||
|
||||
pub fn h6(text: L10n) -> Self {
|
||||
Heading::default()
|
||||
.with_heading_type(HeadingType::H6)
|
||||
.with_text(text)
|
||||
}
|
||||
|
||||
// Heading BUILDER.
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_id(&mut self, id: impl Into<String>) -> &mut Self {
|
||||
self.id.set_value(id);
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_heading_type(&mut self, heading_type: HeadingType) -> &mut Self {
|
||||
self.heading_type = heading_type;
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_size(&mut self, size: HeadingSize) -> &mut Self {
|
||||
self.size = size;
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_text(&mut self, text: L10n) -> &mut Self {
|
||||
self.text.set_value(text);
|
||||
self
|
||||
}
|
||||
|
||||
// Paragraph GETTERS.
|
||||
|
||||
pub fn heading_type(&self) -> &HeadingType {
|
||||
&self.heading_type
|
||||
}
|
||||
|
||||
pub fn size(&self) -> &HeadingSize {
|
||||
&self.size
|
||||
}
|
||||
|
||||
pub fn text(&self) -> &OptionTranslated {
|
||||
&self.text
|
||||
}
|
||||
}
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[derive(AutoDefault, ComponentClasses)]
|
||||
pub struct Icon {
|
||||
classes : OptionClasses,
|
||||
icon_name: OptionString,
|
||||
font_size: FontSize,
|
||||
}
|
||||
|
||||
impl ComponentTrait for Icon {
|
||||
fn new() -> Self {
|
||||
Icon::default()
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
fn setup_before_prepare(&mut self, cx: &mut Context) {
|
||||
if let Some(icon_name) = self.icon_name().get() {
|
||||
self.set_classes(ClassesOp::Prepend,
|
||||
concat_string!("bi-", icon_name, " ", self.font_size().to_string()),
|
||||
);
|
||||
cx.set_param::<bool>(PARAM_BASE_INCLUDE_ICONS, &true);
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_component(&self, _cx: &mut Context) -> PrepareMarkup {
|
||||
match self.icon_name().get() {
|
||||
None => PrepareMarkup::None,
|
||||
_ => PrepareMarkup::With(html! { i class=[self.classes().get()] {} }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Icon {
|
||||
pub fn with(icon_name: impl Into<String>) -> Self {
|
||||
Icon::default().with_icon_name(icon_name)
|
||||
}
|
||||
|
||||
// Icon BUILDER.
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_icon_name(&mut self, name: impl Into<String>) -> &mut Self {
|
||||
self.icon_name.set_value(name);
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_font_size(&mut self, font_size: FontSize) -> &mut Self {
|
||||
self.font_size = font_size;
|
||||
self
|
||||
}
|
||||
|
||||
// Icon GETTERS.
|
||||
|
||||
pub fn icon_name(&self) -> &OptionString {
|
||||
&self.icon_name
|
||||
}
|
||||
|
||||
pub fn font_size(&self) -> &FontSize {
|
||||
&self.font_size
|
||||
}
|
||||
}
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
const IMG_FLUID: &str = "img__fluid";
|
||||
const IMG_FIXED: &str = "img__fixed";
|
||||
|
||||
#[derive(AutoDefault)]
|
||||
pub enum ImageSize {
|
||||
#[default]
|
||||
Auto,
|
||||
Size(u16, u16),
|
||||
Width(u16),
|
||||
Height(u16),
|
||||
Both(u16),
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[derive(AutoDefault, ComponentClasses)]
|
||||
pub struct Image {
|
||||
id : OptionId,
|
||||
classes: OptionClasses,
|
||||
source : OptionString,
|
||||
size : ImageSize,
|
||||
}
|
||||
|
||||
impl ComponentTrait for Image {
|
||||
fn new() -> Self {
|
||||
Image::default().with_classes(ClassesOp::Add, IMG_FLUID)
|
||||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.id.get()
|
||||
}
|
||||
|
||||
fn prepare_component(&self, _cx: &mut Context) -> PrepareMarkup {
|
||||
let (width, height) = match self.size() {
|
||||
ImageSize::Auto => (None, None),
|
||||
ImageSize::Size(width, height) => (Some(width), Some(height)),
|
||||
ImageSize::Width(width) => (Some(width), None),
|
||||
ImageSize::Height(height) => (None, Some(height)),
|
||||
ImageSize::Both(value) => (Some(value), Some(value)),
|
||||
};
|
||||
PrepareMarkup::With(html! {
|
||||
img
|
||||
src=[self.source().get()]
|
||||
id=[self.id()]
|
||||
class=[self.classes().get()]
|
||||
width=[width]
|
||||
height=[height] {}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Image {
|
||||
pub fn with(source: &str) -> Self {
|
||||
Image::default()
|
||||
.with_source(source)
|
||||
.with_classes(ClassesOp::Add, IMG_FLUID)
|
||||
}
|
||||
|
||||
pub fn fixed(source: &str) -> Self {
|
||||
Image::default()
|
||||
.with_source(source)
|
||||
.with_classes(ClassesOp::Add, IMG_FIXED)
|
||||
}
|
||||
|
||||
pub fn pagetop() -> Self {
|
||||
Image::default()
|
||||
.with_source("/base/pagetop-logo.svg")
|
||||
.with_classes(ClassesOp::Add, IMG_FIXED)
|
||||
.with_size(ImageSize::Size(64, 64))
|
||||
}
|
||||
|
||||
// Image BUILDER.
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_id(&mut self, id: impl Into<String>) -> &mut Self {
|
||||
self.id.set_value(id);
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_source(&mut self, source: &str) -> &mut Self {
|
||||
self.source.set_value(source);
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_size(&mut self, size: ImageSize) -> &mut Self {
|
||||
self.size = size;
|
||||
self
|
||||
}
|
||||
|
||||
// Image GETTERS.
|
||||
|
||||
pub fn source(&self) -> &OptionString {
|
||||
&self.source
|
||||
}
|
||||
|
||||
pub fn size(&self) -> &ImageSize {
|
||||
&self.size
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
mod menu_main;
|
||||
pub use menu_main::Menu;
|
||||
|
||||
mod item;
|
||||
pub use item::{Item, ItemType};
|
||||
|
||||
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};
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
use super::Submenu;
|
||||
|
||||
type Content = TypedComponent<Html>;
|
||||
type SubmenuItems = TypedComponent<Submenu>;
|
||||
|
||||
#[derive(AutoDefault)]
|
||||
pub enum ElementType {
|
||||
#[default]
|
||||
Void,
|
||||
Html(Content),
|
||||
Submenu(SubmenuItems),
|
||||
}
|
||||
|
||||
// Element.
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[derive(AutoDefault)]
|
||||
pub struct Element {
|
||||
element_type: ElementType,
|
||||
}
|
||||
|
||||
impl ComponentTrait 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: Submenu) -> Self {
|
||||
Element {
|
||||
element_type: ElementType::Submenu(SubmenuItems::with(submenu)),
|
||||
}
|
||||
}
|
||||
|
||||
// Element GETTERS.
|
||||
|
||||
pub fn element_type(&self) -> &ElementType {
|
||||
&self.element_type
|
||||
}
|
||||
}
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
use super::Element;
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[derive(AutoDefault)]
|
||||
pub struct Group {
|
||||
id : OptionId,
|
||||
elements: MixedComponents,
|
||||
}
|
||||
|
||||
impl ComponentTrait for Group {
|
||||
fn new() -> Self {
|
||||
Group::default()
|
||||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
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.
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_id(&mut self, id: impl Into<String>) -> &mut Self {
|
||||
self.id.set_value(id);
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_elements(&mut self, op: TypedOp<Element>) -> &mut Self {
|
||||
self.elements.set_typed(op);
|
||||
self
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub fn add_element(mut self, element: Element) -> Self {
|
||||
self.elements.set_value(AnyOp::Add(AnyComponent::with(element)));
|
||||
self
|
||||
}
|
||||
|
||||
// Group GETTERS.
|
||||
|
||||
pub fn elements(&self) -> &MixedComponents {
|
||||
&self.elements
|
||||
}
|
||||
}
|
||||
|
|
@ -1,184 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
use super::{Megamenu, Submenu};
|
||||
|
||||
type Label = L10n;
|
||||
type Content = TypedComponent<Html>;
|
||||
type SubmenuItems = TypedComponent<Submenu>;
|
||||
type MegamenuGroups = TypedComponent<Megamenu>;
|
||||
|
||||
#[derive(AutoDefault)]
|
||||
pub enum ItemType {
|
||||
#[default]
|
||||
Void,
|
||||
Label(Label),
|
||||
Link(Label, FnContextualPath),
|
||||
LinkBlank(Label, FnContextualPath),
|
||||
Html(Content),
|
||||
Submenu(Label, SubmenuItems),
|
||||
Megamenu(Label, MegamenuGroups),
|
||||
}
|
||||
|
||||
// Item.
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[derive(AutoDefault)]
|
||||
pub struct Item {
|
||||
item_type : ItemType,
|
||||
description: OptionTranslated,
|
||||
left_icon : OptionComponent<Icon>,
|
||||
right_icon : OptionComponent<Icon>,
|
||||
}
|
||||
|
||||
impl ComponentTrait for Item {
|
||||
fn new() -> Self {
|
||||
Item::default()
|
||||
}
|
||||
|
||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
||||
let description = self.description.using(cx.langid());
|
||||
|
||||
let left_icon = self.left_icon().render(cx);
|
||||
let right_icon = self.right_icon().render(cx);
|
||||
|
||||
match self.item_type() {
|
||||
ItemType::Void => PrepareMarkup::None,
|
||||
ItemType::Label(label) => PrepareMarkup::With(html! {
|
||||
li class="menu__label" {
|
||||
span title=[description] {
|
||||
(left_icon)
|
||||
(label.escaped(cx.langid()))
|
||||
(right_icon)
|
||||
}
|
||||
}
|
||||
}),
|
||||
ItemType::Link(label, path) => PrepareMarkup::With(html! {
|
||||
li class="menu__link" {
|
||||
a href=(path(cx)) title=[description] {
|
||||
(left_icon)
|
||||
(label.escaped(cx.langid()))
|
||||
(right_icon)
|
||||
}
|
||||
}
|
||||
}),
|
||||
ItemType::LinkBlank(label, path) => PrepareMarkup::With(html! {
|
||||
li class="menu__link" {
|
||||
a href=(path(cx)) title=[description] target="_blank" {
|
||||
(left_icon)
|
||||
(label.escaped(cx.langid()))
|
||||
(right_icon)
|
||||
}
|
||||
}
|
||||
}),
|
||||
ItemType::Html(content) => PrepareMarkup::With(html! {
|
||||
li class="menu__html" {
|
||||
(content.render(cx))
|
||||
}
|
||||
}),
|
||||
ItemType::Submenu(label, submenu) => PrepareMarkup::With(html! {
|
||||
li class="menu__children" {
|
||||
a href="#" title=[description] {
|
||||
(left_icon)
|
||||
(label.escaped(cx.langid())) i class="menu__icon bi-chevron-down" {}
|
||||
}
|
||||
div class="menu__subs" {
|
||||
(submenu.render(cx))
|
||||
}
|
||||
}
|
||||
}),
|
||||
ItemType::Megamenu(label, megamenu) => PrepareMarkup::With(html! {
|
||||
li class="menu__children" {
|
||||
a href="#" title=[description] {
|
||||
(left_icon)
|
||||
(label.escaped(cx.langid())) i class="menu__icon bi-chevron-down" {}
|
||||
}
|
||||
div class="menu__subs menu__mega" {
|
||||
(megamenu.render(cx))
|
||||
}
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Item {
|
||||
pub fn label(label: L10n) -> Self {
|
||||
Item {
|
||||
item_type: ItemType::Label(label),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn link(label: L10n, path: FnContextualPath) -> Self {
|
||||
Item {
|
||||
item_type: ItemType::Link(label, path),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn link_blank(label: L10n, path: FnContextualPath) -> Self {
|
||||
Item {
|
||||
item_type: ItemType::LinkBlank(label, path),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn html(content: Html) -> Self {
|
||||
Item {
|
||||
item_type: ItemType::Html(Content::with(content)),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn submenu(label: L10n, submenu: Submenu) -> Self {
|
||||
Item {
|
||||
item_type: ItemType::Submenu(label, SubmenuItems::with(submenu)),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn megamenu(label: L10n, megamenu: Megamenu) -> Self {
|
||||
Item {
|
||||
item_type: ItemType::Megamenu(label, MegamenuGroups::with(megamenu)),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
// Item BUILDER.
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_description(&mut self, text: L10n) -> &mut Self {
|
||||
self.description.set_value(text);
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_left_icon(&mut self, icon: Option<Icon>) -> &mut Self {
|
||||
self.left_icon.set_value(icon);
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_right_icon(&mut self, icon: Option<Icon>) -> &mut Self {
|
||||
self.right_icon.set_value(icon);
|
||||
self
|
||||
}
|
||||
|
||||
// Item GETTERS.
|
||||
|
||||
pub fn item_type(&self) -> &ItemType {
|
||||
&self.item_type
|
||||
}
|
||||
|
||||
pub fn description(&self) -> &OptionTranslated {
|
||||
&self.description
|
||||
}
|
||||
|
||||
pub fn left_icon(&self) -> &OptionComponent<Icon> {
|
||||
&self.left_icon
|
||||
}
|
||||
|
||||
pub fn right_icon(&self) -> &OptionComponent<Icon> {
|
||||
&self.right_icon
|
||||
}
|
||||
}
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
use super::Group;
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[derive(AutoDefault)]
|
||||
pub struct Megamenu {
|
||||
id : OptionId,
|
||||
groups: MixedComponents,
|
||||
}
|
||||
|
||||
impl ComponentTrait for Megamenu {
|
||||
fn new() -> Self {
|
||||
Megamenu::default()
|
||||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.id.get()
|
||||
}
|
||||
|
||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
||||
PrepareMarkup::With(html! {
|
||||
div id=[self.id()] class="menu__groups" {
|
||||
(self.groups().render(cx))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Megamenu {
|
||||
// Megamenu BUILDER.
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_id(&mut self, id: impl Into<String>) -> &mut Self {
|
||||
self.id.set_value(id);
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_groups(&mut self, op: TypedOp<Group>) -> &mut Self {
|
||||
self.groups.set_typed(op);
|
||||
self
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub fn add_group(mut self, group: Group) -> Self {
|
||||
self.groups.set_value(AnyOp::Add(AnyComponent::with(group)));
|
||||
self
|
||||
}
|
||||
|
||||
// Megamenu GETTERS.
|
||||
|
||||
pub fn groups(&self) -> &MixedComponents {
|
||||
&self.groups
|
||||
}
|
||||
}
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
use super::Item;
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[derive(AutoDefault)]
|
||||
pub struct Menu {
|
||||
id : OptionId,
|
||||
items: MixedComponents,
|
||||
}
|
||||
|
||||
impl ComponentTrait for Menu {
|
||||
fn new() -> Self {
|
||||
Menu::default()
|
||||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.id.get()
|
||||
}
|
||||
|
||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
||||
cx.set_param::<bool>(PARAM_BASE_INCLUDE_MENU_ASSETS, &true);
|
||||
cx.set_param::<bool>(PARAM_BASE_INCLUDE_ICONS, &true);
|
||||
|
||||
PrepareMarkup::With(html! {
|
||||
div id=[self.id()] class="menu__container" {
|
||||
div class="menu__content" {
|
||||
div class="menu__main" {
|
||||
div class="menu__overlay" {}
|
||||
nav class="menu__nav" {
|
||||
div class="menu__header" {
|
||||
button type="button" class="menu__arrow" {
|
||||
i class="bi-chevron-left" {}
|
||||
}
|
||||
div class="menu__title" {}
|
||||
button type="button" class="menu__close" {
|
||||
i class="bi-x" {}
|
||||
}
|
||||
}
|
||||
ul class="menu__section" {
|
||||
(self.items().render(cx))
|
||||
}
|
||||
}
|
||||
}
|
||||
button
|
||||
type="button"
|
||||
class="menu__trigger"
|
||||
title=[L10n::l("menu_toggle").using(cx.langid())]
|
||||
{
|
||||
span {} span {} span {}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Menu {
|
||||
// Menu BUILDER.
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_id(&mut self, id: impl Into<String>) -> &mut Self {
|
||||
self.id.set_value(id);
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_items(&mut self, op: TypedOp<Item>) -> &mut Self {
|
||||
self.items.set_typed(op);
|
||||
self
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub fn add_item(mut self, item: Item) -> Self {
|
||||
self.items.set_value(AnyOp::Add(AnyComponent::with(item)));
|
||||
self
|
||||
}
|
||||
|
||||
// Menu GETTERS.
|
||||
|
||||
pub fn items(&self) -> &MixedComponents {
|
||||
&self.items
|
||||
}
|
||||
}
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
use super::Item;
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[derive(AutoDefault)]
|
||||
pub struct Submenu {
|
||||
id : OptionId,
|
||||
title: OptionTranslated,
|
||||
items: MixedComponents,
|
||||
}
|
||||
|
||||
impl ComponentTrait for Submenu {
|
||||
fn new() -> Self {
|
||||
Submenu::default()
|
||||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.id.get()
|
||||
}
|
||||
|
||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
||||
PrepareMarkup::With(html! {
|
||||
div id=[self.id()] class="menu__items" {
|
||||
@if let Some(title) = self.title().using(cx.langid()) {
|
||||
h4 class="menu__title" { (title) }
|
||||
}
|
||||
ul {
|
||||
(self.items().render(cx))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Submenu {
|
||||
// Submenu BUILDER.
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_id(&mut self, id: impl Into<String>) -> &mut Self {
|
||||
self.id.set_value(id);
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_title(&mut self, title: L10n) -> &mut Self {
|
||||
self.title.set_value(title);
|
||||
self
|
||||
}
|
||||
|
||||
#[fn_builder]
|
||||
pub fn set_items(&mut self, op: TypedOp<Item>) -> &mut Self {
|
||||
self.items.set_typed(op);
|
||||
self
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub fn add_item(mut self, item: Item) -> Self {
|
||||
self.items.set_value(AnyOp::Add(AnyComponent::with(item)));
|
||||
self
|
||||
}
|
||||
|
||||
// Submenu GETTERS.
|
||||
|
||||
pub fn title(&self) -> &OptionTranslated {
|
||||
&self.title
|
||||
}
|
||||
|
||||
pub fn items(&self) -> &MixedComponents {
|
||||
&self.items
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue