Refactored project to use Builder pattern.
This commit is contained in:
@@ -1,115 +1,160 @@
|
||||
use crate::html::Render;
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
pub(crate) struct h1 {
|
||||
pub(crate) struct Heading {
|
||||
level: u8,
|
||||
id: String,
|
||||
text: String,
|
||||
classes: Vec<String>,
|
||||
}
|
||||
|
||||
impl Render for h1 {
|
||||
impl Render for Heading {
|
||||
fn render(&self) -> String {
|
||||
format!("<h1>{}</h1>", self.text)
|
||||
let classes = self.classes.join(" ");
|
||||
format!(
|
||||
"<h{} id=\"{}\" class=\"{}\">{}</h{}>",
|
||||
self.level, self.id, classes, self.text, self.level
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl h1 {
|
||||
pub(crate) fn new<T>(text: T) -> Self
|
||||
impl Heading {
|
||||
pub(crate) fn builder() -> HeadingBuilder {
|
||||
HeadingBuilder::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct HeadingBuilder {
|
||||
level: Option<u8>,
|
||||
id: Option<String>,
|
||||
text: Option<String>,
|
||||
classes: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
impl HeadingBuilder {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
level: None,
|
||||
id: None,
|
||||
text: None,
|
||||
classes: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn level(self, level: u8) -> Self {
|
||||
Self {
|
||||
level: Some(level),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn id<T>(self, id: T) -> Self
|
||||
where
|
||||
T: Into<String>,
|
||||
{
|
||||
Self { text: text.into() }
|
||||
Self {
|
||||
id: Some(id.into()),
|
||||
..self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
pub(crate) struct h2 {
|
||||
text: String,
|
||||
}
|
||||
|
||||
impl Render for h2 {
|
||||
fn render(&self) -> String {
|
||||
format!("<h2>{}</h2>", self.text)
|
||||
}
|
||||
}
|
||||
|
||||
impl h2 {
|
||||
pub(crate) fn new<T>(text: T) -> Self
|
||||
pub(crate) fn text<T>(self, text: T) -> Self
|
||||
where
|
||||
T: Into<String>,
|
||||
{
|
||||
Self { text: text.into() }
|
||||
Self {
|
||||
text: Some(text.into()),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn classes<T>(self, classes: Vec<T>) -> Self
|
||||
where
|
||||
T: Into<String>,
|
||||
{
|
||||
Self {
|
||||
classes: Some(classes.into_iter().map(|x| x.into()).collect()),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn build(self) -> Heading {
|
||||
let level = self.level.unwrap_or(1);
|
||||
let id = self.id.unwrap_or(String::new());
|
||||
let text = self.text.unwrap_or(String::new());
|
||||
let classes = self.classes.unwrap_or(Vec::new());
|
||||
Heading {
|
||||
level,
|
||||
id,
|
||||
text,
|
||||
classes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
pub(crate) struct link {
|
||||
pub(crate) struct Link {
|
||||
rel: String,
|
||||
href: String,
|
||||
}
|
||||
|
||||
impl Render for link {
|
||||
impl Render for Link {
|
||||
fn render(&self) -> String {
|
||||
format!("<link rel=\"{}\" href=\"{}\">", self.rel, self.href)
|
||||
}
|
||||
}
|
||||
|
||||
impl link {
|
||||
pub(crate) fn new<T, U>(rel: T, href: U) -> Self
|
||||
where
|
||||
T: Into<String>,
|
||||
U: Into<String>,
|
||||
{
|
||||
Self {
|
||||
rel: rel.into(),
|
||||
href: href.into(),
|
||||
}
|
||||
impl Link {
|
||||
pub(crate) fn builder() -> LinkBuilder {
|
||||
LinkBuilder::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
pub(crate) struct div {
|
||||
id: String,
|
||||
classes: Vec<String>,
|
||||
content: Vec<Box<dyn Render>>,
|
||||
pub(crate) struct LinkBuilder {
|
||||
rel: Option<String>,
|
||||
href: Option<String>,
|
||||
}
|
||||
|
||||
impl Render for div {
|
||||
fn render(&self) -> String {
|
||||
let classes = self.classes.join(" ");
|
||||
format!(
|
||||
"<div id=\"{}\" class=\"{}\">{}</div>",
|
||||
self.id,
|
||||
classes,
|
||||
self.content.render()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl div {
|
||||
pub(crate) fn new<T, U>(id: T, classes: Vec<U>) -> Self
|
||||
where
|
||||
T: Into<String>,
|
||||
U: Into<String> + Clone,
|
||||
{
|
||||
impl LinkBuilder {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
classes: classes.iter().map(|x| x.clone().into()).collect(),
|
||||
content: vec![],
|
||||
rel: None,
|
||||
href: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn append_element(&mut self, element: impl Render + 'static) {
|
||||
self.content.push(Box::new(element));
|
||||
pub(crate) fn rel<T>(self, rel: T) -> Self
|
||||
where
|
||||
T: Into<String>,
|
||||
{
|
||||
Self {
|
||||
rel: Some(rel.into()),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn href<T>(self, href: T) -> Self
|
||||
where
|
||||
T: Into<String>,
|
||||
{
|
||||
Self {
|
||||
href: Some(href.into()),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn build(self) -> Link {
|
||||
let rel = self.rel.unwrap_or(String::new());
|
||||
let href = self.href.unwrap_or(String::new());
|
||||
Link { rel, href }
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
pub(crate) struct p {
|
||||
pub(crate) struct Paragraph {
|
||||
id: String,
|
||||
classes: Vec<String>,
|
||||
text: String,
|
||||
}
|
||||
|
||||
impl Render for p {
|
||||
impl Render for Paragraph {
|
||||
fn render(&self) -> String {
|
||||
let classes = self.classes.join(" ");
|
||||
format!(
|
||||
@@ -119,29 +164,72 @@ impl Render for p {
|
||||
}
|
||||
}
|
||||
|
||||
impl p {
|
||||
pub(crate) fn new<T, U, V>(id: T, classes: Vec<U>, text: V) -> Self
|
||||
where
|
||||
T: Into<String>,
|
||||
U: Into<String> + Clone,
|
||||
V: Into<String>,
|
||||
{
|
||||
Self {
|
||||
id: id.into(),
|
||||
classes: classes.iter().map(|x| x.clone().into()).collect(),
|
||||
text: text.into(),
|
||||
}
|
||||
impl Paragraph {
|
||||
pub(crate) fn builder() -> ParagraphBuilder {
|
||||
ParagraphBuilder::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
pub(crate) struct img {
|
||||
pub(crate) struct ParagraphBuilder {
|
||||
id: Option<String>,
|
||||
classes: Option<Vec<String>>,
|
||||
text: Option<String>,
|
||||
}
|
||||
|
||||
impl ParagraphBuilder {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
id: None,
|
||||
classes: None,
|
||||
text: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn id<T>(self, id: T) -> Self
|
||||
where
|
||||
T: Into<String>,
|
||||
{
|
||||
Self {
|
||||
id: Some(id.into()),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn classes<T>(self, classes: Vec<T>) -> Self
|
||||
where
|
||||
T: Into<String>,
|
||||
{
|
||||
Self {
|
||||
classes: Some(classes.into_iter().map(|x| x.into()).collect()),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn text<T>(self, text: T) -> Self
|
||||
where
|
||||
T: Into<String>,
|
||||
{
|
||||
Self {
|
||||
text: Some(text.into()),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn build(self) -> Paragraph {
|
||||
let id = self.id.unwrap_or(String::new());
|
||||
let classes = self.classes.unwrap_or(Vec::new());
|
||||
let text = self.text.unwrap_or(String::new());
|
||||
Paragraph { id, classes, text }
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Image {
|
||||
id: String,
|
||||
classes: Vec<String>,
|
||||
src: String,
|
||||
}
|
||||
|
||||
impl Render for img {
|
||||
impl Render for Image {
|
||||
fn render(&self) -> String {
|
||||
let classes = self.classes.join(" ");
|
||||
format!(
|
||||
@@ -151,30 +239,73 @@ impl Render for img {
|
||||
}
|
||||
}
|
||||
|
||||
impl img {
|
||||
pub(crate) fn new<T, U, V>(id: T, classes: Vec<U>, src: V) -> Self
|
||||
where
|
||||
T: Into<String>,
|
||||
U: Into<String> + Clone,
|
||||
V: Into<String>,
|
||||
{
|
||||
Self {
|
||||
id: id.into(),
|
||||
classes: classes.iter().map(|x| x.clone().into()).collect(),
|
||||
src: src.into(),
|
||||
}
|
||||
impl Image {
|
||||
pub(crate) fn builder() -> ImageBuilder {
|
||||
ImageBuilder::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
pub(crate) struct a {
|
||||
pub(crate) struct ImageBuilder {
|
||||
id: Option<String>,
|
||||
classes: Option<Vec<String>>,
|
||||
src: Option<String>,
|
||||
}
|
||||
|
||||
impl ImageBuilder {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
id: None,
|
||||
classes: None,
|
||||
src: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn id<T>(self, id: T) -> Self
|
||||
where
|
||||
T: Into<String>,
|
||||
{
|
||||
Self {
|
||||
id: Some(id.into()),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn classes<T>(self, classes: Vec<T>) -> Self
|
||||
where
|
||||
T: Into<String>,
|
||||
{
|
||||
Self {
|
||||
classes: Some(classes.into_iter().map(|x| x.into()).collect()),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn src<T>(self, src: T) -> Self
|
||||
where
|
||||
T: Into<String>,
|
||||
{
|
||||
Self {
|
||||
src: Some(src.into()),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn build(self) -> Image {
|
||||
let id = self.id.unwrap_or(String::new());
|
||||
let classes = self.classes.unwrap_or(Vec::new());
|
||||
let src = self.src.unwrap_or(String::new());
|
||||
Image { id, classes, src }
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Anchor {
|
||||
id: String,
|
||||
classes: Vec<String>,
|
||||
href: String,
|
||||
text: String,
|
||||
}
|
||||
|
||||
impl Render for a {
|
||||
impl Render for Anchor {
|
||||
fn render(&self) -> String {
|
||||
let classes = self.classes.join(" ");
|
||||
format!(
|
||||
@@ -184,19 +315,78 @@ impl Render for a {
|
||||
}
|
||||
}
|
||||
|
||||
impl a {
|
||||
pub(crate) fn new<T, U, V, W>(id: T, classes: Vec<U>, href: V, text: W) -> Self
|
||||
impl Anchor {
|
||||
pub(crate) fn builder() -> AnchorBuilder {
|
||||
AnchorBuilder::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct AnchorBuilder {
|
||||
id: Option<String>,
|
||||
classes: Option<Vec<String>>,
|
||||
href: Option<String>,
|
||||
text: Option<String>,
|
||||
}
|
||||
|
||||
impl AnchorBuilder {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
id: None,
|
||||
classes: None,
|
||||
href: None,
|
||||
text: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn id<T>(self, id: T) -> Self
|
||||
where
|
||||
T: Into<String>,
|
||||
U: Into<String> + Clone,
|
||||
V: Into<String>,
|
||||
W: Into<String>,
|
||||
{
|
||||
Self {
|
||||
id: id.into(),
|
||||
classes: classes.iter().map(|x| x.clone().into()).collect(),
|
||||
href: href.into(),
|
||||
text: text.into(),
|
||||
id: Some(id.into()),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn classes<T>(self, classes: Vec<T>) -> Self
|
||||
where
|
||||
T: Into<String>,
|
||||
{
|
||||
Self {
|
||||
classes: Some(classes.into_iter().map(|x| x.into()).collect()),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn href<T>(self, href: T) -> Self
|
||||
where
|
||||
T: Into<String>,
|
||||
{
|
||||
Self {
|
||||
href: Some(href.into()),
|
||||
..self
|
||||
}
|
||||
}
|
||||
pub(crate) fn text<T>(self, text: T) -> Self
|
||||
where
|
||||
T: Into<String>,
|
||||
{
|
||||
Self {
|
||||
text: Some(text.into()),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn build(self) -> Anchor {
|
||||
let id = self.id.unwrap_or(String::new());
|
||||
let classes = self.classes.unwrap_or(Vec::new());
|
||||
let href = self.href.unwrap_or(String::new());
|
||||
let text = self.text.unwrap_or(String::new());
|
||||
Anchor {
|
||||
id,
|
||||
classes,
|
||||
href,
|
||||
text,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
83
src/html/layouts.rs
Normal file
83
src/html/layouts.rs
Normal file
@@ -0,0 +1,83 @@
|
||||
use crate::html::Render;
|
||||
|
||||
pub(crate) struct Division {
|
||||
id: String,
|
||||
classes: Vec<String>,
|
||||
elements: Vec<Box<dyn Render>>,
|
||||
}
|
||||
|
||||
impl Render for Division {
|
||||
fn render(&self) -> String {
|
||||
let classes = self.classes.join(" ");
|
||||
format!(
|
||||
"<div id=\"{}\" class=\"{}\">{}</div>",
|
||||
self.id,
|
||||
classes,
|
||||
self.elements.render()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Division {
|
||||
pub(crate) fn builder() -> DivisionBuilder {
|
||||
DivisionBuilder::new()
|
||||
}
|
||||
|
||||
pub(crate) fn append_element(&mut self, element: impl Render + 'static) {
|
||||
self.elements.push(Box::new(element));
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct DivisionBuilder {
|
||||
id: Option<String>,
|
||||
classes: Option<Vec<String>>,
|
||||
elements: Option<Vec<Box<dyn Render>>>,
|
||||
}
|
||||
|
||||
impl DivisionBuilder {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
id: None,
|
||||
classes: None,
|
||||
elements: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn id<T>(self, id: T) -> Self
|
||||
where
|
||||
T: Into<String>,
|
||||
{
|
||||
Self {
|
||||
id: Some(id.into()),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn classes<T>(self, classes: Vec<T>) -> Self
|
||||
where
|
||||
T: Into<String>,
|
||||
{
|
||||
Self {
|
||||
classes: Some(classes.into_iter().map(|x| x.into()).collect()),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn elements(self, elements: Vec<Box<dyn Render>>) -> Self {
|
||||
Self {
|
||||
elements: Some(elements),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn build(self) -> Division {
|
||||
let id = self.id.unwrap_or(String::new());
|
||||
let classes = self.classes.unwrap_or(Vec::new());
|
||||
let elements = self.elements.unwrap_or(Vec::new());
|
||||
Division {
|
||||
id,
|
||||
classes,
|
||||
elements,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,92 +1,31 @@
|
||||
pub(crate) mod elements;
|
||||
pub(crate) mod layouts;
|
||||
pub(crate) mod pages;
|
||||
|
||||
macro_rules! boxed_vec {
|
||||
($($element:ident),+) => {
|
||||
vec![
|
||||
$(
|
||||
Box::new($element) as Box<dyn Render>,
|
||||
)*
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) use boxed_vec;
|
||||
|
||||
pub(crate) trait Render {
|
||||
fn render(&self) -> String;
|
||||
}
|
||||
|
||||
pub(crate) struct Page {
|
||||
title: String,
|
||||
head: Vec<Box<dyn Render>>,
|
||||
body: Vec<Box<dyn Render>>,
|
||||
}
|
||||
|
||||
pub(crate) struct PageBuilder {
|
||||
title: Option<String>,
|
||||
head: Option<Vec<Box<dyn Render>>>
|
||||
body: Option<Vec<Box<dyn Render>>>
|
||||
}
|
||||
|
||||
impl PageBuilder {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
title: None,
|
||||
head: None,
|
||||
body: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn title<T>(mut self, title: T)
|
||||
where T: Into<String>
|
||||
{
|
||||
self.title = Some(title.into());
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for Vec<Box<dyn Render>> {
|
||||
fn render(&self) -> String {
|
||||
let mut result = String::new();
|
||||
for element in self {
|
||||
let render = element.render();
|
||||
result.push_str(&render);
|
||||
result.push('\n');
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for Page {
|
||||
fn render(&self) -> String {
|
||||
format!(
|
||||
"\
|
||||
<!DOCTYPE html>
|
||||
<html lang=\"en\">
|
||||
<head>
|
||||
<meta charset=\"UTF-8\">
|
||||
<title>{}</title>
|
||||
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />
|
||||
{}
|
||||
</head>
|
||||
<body>
|
||||
{}
|
||||
</body>
|
||||
</html>",
|
||||
self.title,
|
||||
self.head.render(),
|
||||
self.body.render()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Page {
|
||||
pub(crate) fn builder() -> PageBuilder {
|
||||
PageBuilder::new()
|
||||
}
|
||||
|
||||
pub(crate) fn new<T>(title: T) -> Self
|
||||
where
|
||||
T: Into<String>,
|
||||
{
|
||||
Page {
|
||||
title: title.into(),
|
||||
head: vec![],
|
||||
body: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn append_element_to_body(&mut self, element: impl Render + 'static) {
|
||||
self.body.push(Box::new(element));
|
||||
}
|
||||
|
||||
pub(crate) fn append_element_to_head(&mut self, element: impl Render + 'static) {
|
||||
self.head.push(Box::new(element));
|
||||
}
|
||||
}
|
||||
|
||||
199
src/html/pages.rs
Normal file
199
src/html/pages.rs
Normal file
@@ -0,0 +1,199 @@
|
||||
use crate::html::Render;
|
||||
use crate::html::boxed_vec;
|
||||
use crate::html::elements::{Anchor, Image, Link, Paragraph};
|
||||
use crate::html::layouts::Division;
|
||||
|
||||
pub(crate) struct Page {
|
||||
title: String,
|
||||
head: Vec<Box<dyn Render>>,
|
||||
body: Vec<Box<dyn Render>>,
|
||||
}
|
||||
|
||||
impl Render for Page {
|
||||
fn render(&self) -> String {
|
||||
format!(
|
||||
"\
|
||||
<!DOCTYPE html>
|
||||
<html lang=\"en\">
|
||||
<head>
|
||||
<meta charset=\"UTF-8\">
|
||||
<title>{}</title>
|
||||
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />
|
||||
{}
|
||||
</head>
|
||||
<body>
|
||||
{}
|
||||
</body>
|
||||
</html>",
|
||||
self.title,
|
||||
self.head.render(),
|
||||
self.body.render()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Page {
|
||||
pub(crate) fn builder() -> PageBuilder {
|
||||
PageBuilder::new()
|
||||
}
|
||||
|
||||
pub(crate) fn append_element_to_body(&mut self, element: impl Render + 'static) {
|
||||
self.body.push(Box::new(element));
|
||||
}
|
||||
|
||||
pub(crate) fn append_element_to_head(&mut self, element: impl Render + 'static) {
|
||||
self.head.push(Box::new(element));
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct PageBuilder {
|
||||
title: Option<String>,
|
||||
head: Option<Vec<Box<dyn Render>>>,
|
||||
body: Option<Vec<Box<dyn Render>>>,
|
||||
}
|
||||
|
||||
impl PageBuilder {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
title: None,
|
||||
head: None,
|
||||
body: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn title<T>(self, title: T) -> Self
|
||||
where
|
||||
T: Into<String>,
|
||||
{
|
||||
Self {
|
||||
title: Some(title.into()),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn head(self, head: Vec<Box<dyn Render>>) -> Self {
|
||||
Self {
|
||||
head: Some(head),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn body(self, body: Vec<Box<dyn Render>>) -> Self {
|
||||
Self {
|
||||
body: Some(body),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn build(self) -> Page {
|
||||
let title = self.title.unwrap_or(String::new());
|
||||
let head = self.head.unwrap_or(Vec::new());
|
||||
let body = self.body.unwrap_or(Vec::new());
|
||||
Page { title, head, body }
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct BasePage {
|
||||
page: Page,
|
||||
}
|
||||
|
||||
impl BasePage {
|
||||
pub(crate) fn builder() -> BasePageBuilder {
|
||||
BasePageBuilder::new()
|
||||
}
|
||||
|
||||
pub(crate) fn append_element_to_head(&mut self, element: impl Render + 'static) {
|
||||
self.page.append_element_to_head(element);
|
||||
}
|
||||
pub(crate) fn append_element_to_body(&mut self, element: impl Render + 'static) {
|
||||
self.page.append_element_to_body(element);
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for BasePage {
|
||||
fn render(&self) -> String {
|
||||
self.page.render()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct BasePageBuilder {
|
||||
title: Option<String>,
|
||||
head: Option<Vec<Box<dyn Render>>>,
|
||||
body: Option<Vec<Box<dyn Render>>>,
|
||||
}
|
||||
|
||||
impl BasePageBuilder {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
title: None,
|
||||
head: None,
|
||||
body: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn title<T>(self, title: T) -> Self
|
||||
where
|
||||
T: Into<String>,
|
||||
{
|
||||
Self {
|
||||
title: Some(title.into()),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn head(self, head: Vec<Box<dyn Render>>) -> Self {
|
||||
Self {
|
||||
head: Some(head),
|
||||
..self
|
||||
}
|
||||
}
|
||||
pub(crate) fn body(self, body: Vec<Box<dyn Render>>) -> Self {
|
||||
Self {
|
||||
body: Some(body),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn build(self) -> BasePage {
|
||||
let title = self.title.unwrap_or(String::new());
|
||||
let head = self.head.unwrap_or(Vec::new());
|
||||
let body = self.body.unwrap_or(Vec::new());
|
||||
let name = Paragraph::builder()
|
||||
.id("name")
|
||||
.classes(vec!["name"])
|
||||
.text("AINDUSTRIES")
|
||||
.build();
|
||||
let home = Anchor::builder().id("home").classes(vec!["nav-button"]).href("/").text("Home").build();
|
||||
let projects = Anchor::builder().id("projects").classes(vec!["nav-button"]).href("/projects").text("Projects").build();
|
||||
let buttons = Division::builder()
|
||||
.classes(vec!["nav-buttons"])
|
||||
.elements(boxed_vec![home, projects])
|
||||
.build();
|
||||
let header = Division::builder()
|
||||
.id("header")
|
||||
.classes(vec!["header"])
|
||||
.elements(boxed_vec![name, buttons])
|
||||
.build();
|
||||
// background
|
||||
let image = Image::builder()
|
||||
.id("background")
|
||||
.classes(vec!["background"])
|
||||
.src("/static/img/background.png")
|
||||
.build();
|
||||
// css
|
||||
let base = Link::builder()
|
||||
.rel("stylesheet")
|
||||
.href("/static/css/base.css")
|
||||
.build();
|
||||
let mut final_head = boxed_vec![base];
|
||||
final_head.extend(head);
|
||||
let mut final_body = boxed_vec![image, header];
|
||||
final_body.extend(body);
|
||||
let page = Page::builder()
|
||||
.title(title)
|
||||
.head(final_head)
|
||||
.body(final_body)
|
||||
.build();
|
||||
BasePage { page }
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,12 @@ use pages::{
|
||||
#[derive(Parser)]
|
||||
#[command(name = "aindustries-be", version, about = "This is the amazing webserver for aindustries.be!", long_about = None)]
|
||||
struct Cli {
|
||||
#[arg(short, long, value_name = "CONFIG", help = "Path to config file, defaults to /etc/aindustries-be/config.toml")]
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
value_name = "CONFIG",
|
||||
help = "Path to config file, defaults to /etc/aindustries-be/config.toml"
|
||||
)]
|
||||
config: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,76 +1,39 @@
|
||||
pub(crate) mod files;
|
||||
pub(crate) mod projects;
|
||||
|
||||
use crate::html::elements::{a, div, h1, h2, img, link, p};
|
||||
use crate::html::{Page, Render};
|
||||
use crate::html::elements::{Heading, Link};
|
||||
use crate::html::layouts::Division;
|
||||
use crate::html::pages::BasePage;
|
||||
use crate::html::{Render, boxed_vec};
|
||||
use actix_web::{Responder, get};
|
||||
|
||||
pub(crate) struct BasePage {
|
||||
page: Page,
|
||||
}
|
||||
|
||||
impl BasePage {
|
||||
pub(crate) fn new<T>(title: T) -> Self
|
||||
where
|
||||
T: Into<String>,
|
||||
{
|
||||
let mut page = Page::new(title);
|
||||
// header
|
||||
let mut header = div::new("header", vec!["header"]);
|
||||
let name = p::new("name", vec!["name"], "AINDUSTRIES");
|
||||
let mut buttons = div::new("buttons", vec!["nav-buttons"]);
|
||||
let home = a::new("home", vec!["nav-button"], "/", "Home");
|
||||
let projects = a::new("projects", vec!["nav-button"], "/projects", "Projects");
|
||||
buttons.append_element(home);
|
||||
buttons.append_element(projects);
|
||||
header.append_element(name);
|
||||
header.append_element(buttons);
|
||||
// background
|
||||
let image = img::new(
|
||||
"background",
|
||||
vec!["background"],
|
||||
"/static/img/background.png",
|
||||
);
|
||||
// css
|
||||
let base = link::new("stylesheet", "/static/css/base.css");
|
||||
// add elements to the page in order
|
||||
page.append_element_to_head(base);
|
||||
page.append_element_to_body(image);
|
||||
page.append_element_to_body(header);
|
||||
Self { page }
|
||||
}
|
||||
|
||||
pub(crate) fn append_element_to_head(&mut self, element: impl Render + 'static) {
|
||||
self.page.append_element_to_head(element);
|
||||
}
|
||||
pub(crate) fn append_element_to_body(&mut self, element: impl Render + 'static) {
|
||||
self.page.append_element_to_body(element);
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for BasePage {
|
||||
fn render(&self) -> String {
|
||||
self.page.render()
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
async fn index() -> impl Responder {
|
||||
let mut page = BasePage::new("AINDUSTRIES");
|
||||
// introduction
|
||||
let mut intro = div::new("intro", vec!["intro"]);
|
||||
let hi = h1::new("Hello and welcome!");
|
||||
let detail = h2::new(
|
||||
"This here is a small website to show the passion I have for computers.<br>\
|
||||
let hi = Heading::builder().text("Hello and welcome!").build();
|
||||
let detail = Heading::builder()
|
||||
.level(2)
|
||||
.text(
|
||||
"This here is a small website to show the passion I have for computers.<br>\
|
||||
I have always had a good time creating and discovering new things.<br>\
|
||||
Your may discover some of my projects here on this showcase.",
|
||||
);
|
||||
intro.append_element(hi);
|
||||
intro.append_element(detail);
|
||||
Your may discover some of my projects here on this little showcase.",
|
||||
)
|
||||
.build();
|
||||
let intro = Division::builder()
|
||||
.id("intro")
|
||||
.classes(vec!["intro"])
|
||||
.elements(boxed_vec![hi, detail])
|
||||
.build();
|
||||
// css
|
||||
let index = link::new("stylesheet", "static/css/index.css");
|
||||
let index = Link::builder()
|
||||
.rel("stylesheet")
|
||||
.href("static/css/index.css")
|
||||
.build();
|
||||
// add elements to the page in order
|
||||
page.append_element_to_head(index);
|
||||
page.append_element_to_body(intro);
|
||||
let page = BasePage::builder()
|
||||
.title("AINDUSTRIES")
|
||||
.head(boxed_vec![index])
|
||||
.body(boxed_vec![intro])
|
||||
.build();
|
||||
page.render()
|
||||
}
|
||||
|
||||
@@ -1,64 +1,70 @@
|
||||
use super::BasePage;
|
||||
use crate::html::Render;
|
||||
use crate::html::elements::{a, div, h1, h2, link, p};
|
||||
use crate::html::elements::{Anchor, Heading, Link, Paragraph};
|
||||
use crate::html::layouts::Division;
|
||||
use crate::html::pages::BasePage;
|
||||
use crate::html::{Render, boxed_vec};
|
||||
use actix_web::{Responder, get, web};
|
||||
|
||||
#[get("/projects")]
|
||||
async fn projects() -> impl Responder {
|
||||
let mut page = BasePage::new("Projects");
|
||||
let title = h1::new("My projects");
|
||||
let desc = h2::new(
|
||||
"Here you will find all my projects which deserve to be shown<br>\
|
||||
let title = Heading::builder().text("My projects").build();
|
||||
let desc = Heading::builder()
|
||||
.level(2)
|
||||
.text(
|
||||
"Here you will find all my projects which deserve to be shown<br>\
|
||||
(I've done a lot of small projects but they are not worth it.)",
|
||||
);
|
||||
let mut website = div::new("project-website", vec!["project"]);
|
||||
let website_title = h1::new("Website");
|
||||
let mut info = div::new("project-website-info", vec!["project-info"]);
|
||||
let website_desc = p::new(
|
||||
"website-desc",
|
||||
vec!["project-desc"],
|
||||
"This project is the website you currently are on.",
|
||||
);
|
||||
let view = a::new(
|
||||
"website-view",
|
||||
vec!["project-view"],
|
||||
"/projects/website",
|
||||
"Learn More",
|
||||
);
|
||||
info.append_element(website_desc);
|
||||
info.append_element(view);
|
||||
website.append_element(website_title);
|
||||
website.append_element(info);
|
||||
let css = link::new("stylesheet", "/static/css/projects.css");
|
||||
page.append_element_to_head(css);
|
||||
page.append_element_to_body(title);
|
||||
page.append_element_to_body(desc);
|
||||
page.append_element_to_body(website);
|
||||
)
|
||||
.build();
|
||||
let website_title = Heading::builder().text("Website").build();
|
||||
let website_desc = Paragraph::builder()
|
||||
.classes(vec!["project-desc"])
|
||||
.text("This project is the website you currently are on.")
|
||||
.build();
|
||||
let view = Anchor::builder().classes(vec!["project-view"]).href("/projects/website").text("Learn More").build();
|
||||
let info = Division::builder()
|
||||
.classes(vec!["project-info"])
|
||||
.elements(boxed_vec![website_desc, view])
|
||||
.build();
|
||||
let website = Division::builder()
|
||||
.classes(vec!["project"])
|
||||
.elements(boxed_vec![website_title, info])
|
||||
.build();
|
||||
let css = Link::builder()
|
||||
.rel("stylesheet")
|
||||
.href("/static/css/projects.css")
|
||||
.build();
|
||||
let page = BasePage::builder()
|
||||
.title("Projects")
|
||||
.head(boxed_vec![css])
|
||||
.body(boxed_vec![title, desc, website])
|
||||
.build();
|
||||
page.render()
|
||||
}
|
||||
#[get("/projects/{project}")]
|
||||
async fn project(project: web::Path<String>) -> impl Responder {
|
||||
let project = project.into_inner();
|
||||
let mut page = BasePage::new(format!("Project-{}", project));
|
||||
let css = Link::builder()
|
||||
.rel("stylesheet")
|
||||
.href("/static/css/project.css")
|
||||
.build();
|
||||
let mut page = BasePage::builder()
|
||||
.title(format!("Project-{}", project))
|
||||
.head(boxed_vec![css])
|
||||
.build();
|
||||
match project.as_str() {
|
||||
"website" => {
|
||||
let title = h1::new("Website");
|
||||
let desc = p::new(
|
||||
"description",
|
||||
vec!["description"],
|
||||
let title = Heading::builder().text("Website").build();
|
||||
let desc = Paragraph::builder().classes(vec!["description"]).text(
|
||||
"This project, the website you are on, \
|
||||
is made in Rust such that all the pages are generated by code.<br>\
|
||||
That is that each html element is represented by a struct which implements the Render trait (as in render the element to html).<br>\
|
||||
As it is right now the system is not that impressive but I believe it can do amazing things in the futur.<br>\
|
||||
<br>\
|
||||
Wish to see more? Check out the gitea repository: ",
|
||||
);
|
||||
).build();
|
||||
page.append_element_to_body(title);
|
||||
page.append_element_to_body(desc);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
let css = link::new("stylesheet", "/static/css/project.css");
|
||||
page.append_element_to_head(css);
|
||||
page.render()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user