Compare commits

...

4 Commits

Author SHA1 Message Date
e63edda9ca Added Projects page 2025-10-06 16:46:49 +02:00
a457efb97e Added middleware for mimetype 2025-10-06 16:46:06 +02:00
4eaf54dd8f Move common css to base 2025-10-06 16:45:45 +02:00
94b68982f6 Modified elements to take static str (will change to &str) 2025-10-06 16:45:07 +02:00
10 changed files with 234 additions and 102 deletions

View File

@@ -7,4 +7,5 @@ edition = "2024"
actix-web = {version = "4.11.0"} actix-web = {version = "4.11.0"}
serde = "1.0.228" serde = "1.0.228"
serde_json = "1.0.145" serde_json = "1.0.145"
sqlx = {version = "0.8.6", features = ["postgres"]} sqlx = {version = "0.8.6", features = ["postgres"]}
futures-util = "0.3.31"

View File

@@ -59,6 +59,54 @@
object-fit: cover; object-fit: cover;
} }
.header {
display: flex;
flex-direction: row;
height: 40px;
/* width: 100%; */
background-color: var(--clr-primary-a0);
border-radius: 8px;
filter: drop-shadow(0 0 8px rgba(0, 0, 0, 0.2));
margin: 8px;
}
.name {
color: var(--clr-light-a0);
font-size: 18px;
margin: auto 0 auto 8px;
font-family: Lexend, serif;
}
.nav-buttons {
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
width: 100%;
height: 100%;
margin: 0 8px 0 auto;
}
.nav-button {
padding: 4px 8px 4px 8px;
border-radius: 8px;
filter: drop-shadow(0 0 8px rgba(0, 0, 0, 0.2));
background-color: transparent;
color: white;
border: none;
font-size: 18px;
font-family: Lexend, serif;
}
.nav-button:hover {
text-decoration-line: underline;
text-decoration-style: wavy;
background-color: var(--clr-primary-a50);
color: purple;
text-decoration-color: purple;
}
body { body {
background-color: #32004f; background-color: #32004f;
margin: 0; margin: 0;

View File

@@ -1,21 +1,3 @@
.header {
display: flex;
flex-direction: row;
height: 40px;
/* width: 100%; */
background-color: var(--clr-primary-a0);
border-radius: 8px;
filter: drop-shadow(0 0 8px rgba(0, 0, 0, 0.2));
margin: 8px;
}
.name {
color: var(--clr-light-a0);
font-size: 18px;
margin: auto 0 auto 8px;
font-family: Lexend, serif;
}
.intro { .intro {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -23,41 +5,6 @@
max-width: 75vw; max-width: 75vw;
} }
.nav-buttons {
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
width: 100%;
height: 100%;
margin: 0 8px 0 auto;
}
.nav-button {
padding: 4px 8px 4px 8px;
border-radius: 8px;
filter: drop-shadow(0 0 8px rgba(0, 0, 0, 0.2));
background-color: transparent;
color: white;
border: none;
font-size: 18px;
font-family: Lexend, serif;
}
.nav-button:hover {
text-decoration-line: underline;
text-decoration-style: wavy;
background-color: var(--clr-primary-a50);
color: purple;
text-decoration-color: purple;
}
.note {
color: gray;
font-size: 18px;
text-align: center;
}
h1 { h1 {
font-size: 68px; font-size: 68px;
font-family: 'Indie Flower', cursive; font-family: 'Indie Flower', cursive;

14
assets/css/projects.css Normal file
View File

@@ -0,0 +1,14 @@
.project {
display: flex;
flex-direction: column;
background-color: rgba(50,50,75,0.8);
border-radius: 12px;
}
.project > h1 {
color: white;
}
.project-desc {
color: white;
}

View File

@@ -36,8 +36,8 @@ impl h2 {
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
pub(crate) struct link { pub(crate) struct link {
rel: String, rel: &'static str,
href: String, href: &'static str,
} }
impl Render for link { impl Render for link {
@@ -47,15 +47,15 @@ impl Render for link {
} }
impl link { impl link {
pub(crate) fn new(rel: String, href: String) -> Self { pub(crate) fn new(rel: &'static str, href: &'static str) -> Self {
Self { rel, href } Self { rel, href }
} }
} }
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
pub(crate) struct div { pub(crate) struct div {
id: String, id: &'static str,
classes: Vec<String>, classes: Vec<&'static str>,
content: Vec<Box<dyn Render>>, content: Vec<Box<dyn Render>>,
} }
@@ -72,7 +72,7 @@ impl Render for div {
} }
impl div { impl div {
pub(crate) fn new(id: String, classes: Vec<String>) -> Self { pub(crate) fn new(id: &'static str, classes: Vec<&'static str>) -> Self {
Self { Self {
id, id,
classes, classes,
@@ -87,8 +87,8 @@ impl div {
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
pub(crate) struct p { pub(crate) struct p {
id: String, id: &'static str,
classes: Vec<String>, classes: Vec<&'static str>,
text: String text: String
} }
@@ -100,7 +100,7 @@ impl Render for p {
} }
impl p { impl p {
pub(crate) fn new(id: String, classes: Vec<String>, text: String) -> Self { pub(crate) fn new(id: &'static str, classes: Vec<&'static str>, text: String) -> Self {
Self { Self {
id, id,
classes, classes,
@@ -111,9 +111,9 @@ impl p {
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
pub(crate) struct img { pub(crate) struct img {
id: String, id: &'static str,
classes: Vec<String>, classes: Vec<&'static str>,
src: String, src: &'static str,
} }
impl Render for img { impl Render for img {
@@ -124,16 +124,16 @@ impl Render for img {
} }
impl img { impl img {
pub(crate) fn new(id: String, classes: Vec<String>, src: String) -> Self { pub(crate) fn new(id: &'static str, classes: Vec<&'static str>, src: &'static str) -> Self {
Self { id, classes, src } Self { id, classes, src }
} }
} }
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
pub(crate) struct a { pub(crate) struct a {
id: String, id: &'static str,
classes: Vec<String>, classes: Vec<&'static str>,
href: String, href: &'static str,
text: String, text: String,
} }
@@ -145,7 +145,7 @@ impl Render for a {
} }
impl a { impl a {
pub(crate) fn new(id: String, classes: Vec<String>, href: String, text: String) -> Self { pub(crate) fn new(id: &'static str, classes: Vec<&'static str>, href: &'static str, text: String) -> Self {
Self { id, classes, href, text } Self { id, classes, href, text }
} }
} }

View File

@@ -5,7 +5,7 @@ pub(crate) trait Render {
} }
pub(crate) struct Page { pub(crate) struct Page {
title: String, title: &'static str,
head: Vec<Box<dyn Render>>, head: Vec<Box<dyn Render>>,
body: Vec<Box<dyn Render>>, body: Vec<Box<dyn Render>>,
} }
@@ -45,7 +45,7 @@ impl Render for Page {
} }
impl Page { impl Page {
pub(crate) fn new(title: String) -> Self { pub(crate) fn new(title: &'static str) -> Self {
Page { Page {
title, title,
head: vec![], head: vec![],

View File

@@ -1,15 +1,25 @@
mod html; mod html;
mod pages; mod pages;
mod middleware;
use actix_web::{App, HttpServer}; use actix_web::{web, App, HttpServer};
use pages::{files::file, index}; use pages::{files::file, index, projects::{projects, project}};
use middleware::MimeType;
#[actix_web::main] #[actix_web::main]
async fn main() { async fn main() {
let bind_address = "0.0.0.0:8080"; let bind_address = "0.0.0.0:8080";
if let Ok(server) = if let Ok(server) =
HttpServer::new(|| App::new().service(index).service(file)).bind(bind_address) HttpServer::new(|| App::new()
.service(file)
.service(web::scope("")
.wrap(MimeType::new("text/html".to_string()))
.service(index)
.service(projects)
.service(project)
)
).bind(bind_address)
{ {
match server.run().await { match server.run().await {
Ok(_) => {} Ok(_) => {}

63
src/middleware/mod.rs Normal file
View File

@@ -0,0 +1,63 @@
use std::future::{ready, Ready};
use actix_web::dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform};
use actix_web::Error;
use actix_web::http::header::{HeaderValue, CONTENT_TYPE};
use futures_util::future::LocalBoxFuture;
pub(crate) struct MimeType {
mime_type: String,
}
impl MimeType {
pub(crate) fn new(mime_type: String) -> Self {
Self {
mime_type
}
}
}
impl<S,B> Transform<S, ServiceRequest> for MimeType
where
S: Service<ServiceRequest, Response=ServiceResponse<B>, Error=Error>,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type Transform = MimeTypeMiddleware<S>;
type InitError = ();
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
let mime_type = self.mime_type.clone();
ready(Ok(MimeTypeMiddleware{service, mime_type}))
}
}
pub(crate) struct MimeTypeMiddleware<S> {
service: S,
mime_type: String,
}
impl<S,B> Service<ServiceRequest> for MimeTypeMiddleware<S>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse<B>;
type Error = Error;
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
forward_ready!(service);
fn call(&self, req: ServiceRequest) -> Self::Future {
let fut = self.service.call(req);
let val = HeaderValue::from_str(self.mime_type.as_str()).unwrap_or_else(|_| HeaderValue::from_static("text/html"));
Box::pin(async move {
let mut res = fut.await?;
res.headers_mut().append(CONTENT_TYPE, val);
Ok(res)
})
}
}

View File

@@ -1,45 +1,69 @@
pub(crate) mod files; pub(crate) mod files;
pub(crate) mod projects;
use crate::html::elements::{a, div, h1, h2, img, link, p}; use crate::html::elements::{a, div, h1, h2, img, link, p};
use crate::html::{Page, Render}; use crate::html::{Page, Render};
use actix_web::http::header::ContentType; use actix_web::{Responder, get};
use actix_web::mime::TEXT_HTML;
use actix_web::{HttpResponse, Responder, get}; pub(crate) struct BasePage {
page: Page
}
impl BasePage {
pub(crate) fn new(title: &'static str) -> Self {
let mut page = Page::new(title);
// header
let mut header = div::new("header", vec!["header"]);
let name = p::new("name", vec!["name"], "AINDUSTRIES".to_string());
let mut buttons = div::new("buttons", vec!["nav-buttons"]);
let home = a::new("home", vec!["nav-button"], "/", "Home".to_string());
let projects = a::new("projects", vec!["nav-button"], "/projects", "Projects".to_string());
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("/")] #[get("/")]
async fn index() -> impl Responder { async fn index() -> impl Responder {
let mut page = Page::new("AINDUSTRIES".to_string()); let mut page = BasePage::new("AINDUSTRIES");
// header
let mut header = div::new("header".to_string(), vec!["header".to_string()]);
let name = p::new("name".to_string(), vec!["name".to_string()], "AINDUSTRIES".to_string());
let mut buttons = div::new("buttons".to_string(), vec!["nav-buttons".to_string()]);
let home = a::new("home".to_string(), vec!["nav-button".to_string()], "/".to_string(), "Home".to_string());
buttons.append_element(home);
header.append_element(name);
header.append_element(buttons);
// background
let image = img::new("background".to_string(), vec!["background".to_string()], "static/img/background.png".to_string());
// introduction // introduction
let mut intro = div::new("intro".to_string(), vec!["intro".to_string()]); let mut intro = div::new("intro", vec!["intro"]);
let hi = h1::new("Hello and welcome!".to_string()); let hi = h1::new("Hello and welcome!".to_string());
let detail = h2::new("This here is a small website to show the passion I have for computers.<br>\ let detail = h2::new("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>\ I have always had a good time creating and discovering new things.<br>\
Your may discover some of my projects here on this showcase.".to_string()); Your may discover some of my projects here on this showcase.".to_string());
let note = p::new("note".to_string(), vec!["note".to_string()], "Website in construction.".to_string());
intro.append_element(hi); intro.append_element(hi);
intro.append_element(detail); intro.append_element(detail);
intro.append_element(note);
// css // css
let base = link::new("stylesheet".to_string(), "static/css/base.css".to_string()); let index = link::new("stylesheet", "static/css/index.css");
let index = link::new("stylesheet".to_string(), "static/css/index.css".to_string());
// add elements to the page in order // add elements to the page in order
page.append_element_to_head(base);
page.append_element_to_head(index); page.append_element_to_head(index);
page.append_element_to_body(image);
page.append_element_to_body(header);
page.append_element_to_body(intro); page.append_element_to_body(intro);
let response = HttpResponse::Ok() page.render()
.insert_header(ContentType(TEXT_HTML))
.body(page.render());
response
} }

25
src/pages/projects.rs Normal file
View File

@@ -0,0 +1,25 @@
use actix_web::{get, Responder};
use crate::html::elements::{div, h1, p, a, link};
use crate::html::{Render};
use super::BasePage;
#[get("/projects")]
async fn projects() -> impl Responder {
let mut page = BasePage::new("Projects");
let mut website = div::new("project-website", vec!["project"]);
let title = h1::new("Website".to_string());
let desc = p::new("website-desc", vec!["project-desc"], "This project is the website you are currently on.".to_string());
let view = a::new("website-view", vec!["project-view"], "website" , "View More".to_string());
website.append_element(title);
website.append_element(desc);
website.append_element(view);
let css = link::new("stylesheet", "/static/css/projects.css");
page.append_element_to_head(css);
page.append_element_to_body(website);
page.render()
}
#[get("/projects/{project}")]
async fn project() -> impl Responder {
let page = BasePage::new("Project");
page.render()
}