Compare commits

...

5 Commits

Author SHA1 Message Date
cba44c144d Added routes to app 2025-10-05 23:29:29 +02:00
71911f2662 Added assets 2025-10-05 23:29:22 +02:00
4b10832591 Added main page and static files 2025-10-05 23:29:11 +02:00
af6fb8fd68 Added html elements 2025-10-05 23:29:00 +02:00
59305ce83c Added dependencies 2025-10-05 23:28:44 +02:00
9 changed files with 457 additions and 2 deletions

View File

@@ -4,3 +4,7 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
actix-web = {version = "4.11.0"}
serde = "1.0.228"
serde_json = "1.0.145"
sqlx = {version = "0.8.6", features = ["postgres"]}

71
assets/css/base.css Normal file
View File

@@ -0,0 +1,71 @@
@import url('https://fonts.googleapis.com/css2?family=Indie+Flower&family=Lexend:wght@100..900&display=swap');
:root {
/** Base colors */
--clr-dark-a0: #000000;
--clr-light-a0: #ffffff;
/** Theme primary colors */
--clr-primary-a0: #aa50b2;
--clr-primary-a10: #b464bb;
--clr-primary-a20: #bf78c3;
--clr-primary-a30: #c98bcc;
--clr-primary-a40: #d29ed4;
--clr-primary-a50: #dcb1dd;
/** Theme surface colors */
--clr-surface-a0: #121212;
--clr-surface-a10: #282828;
--clr-surface-a20: #3f3f3f;
--clr-surface-a30: #575757;
--clr-surface-a40: #717171;
--clr-surface-a50: #8b8b8b;
/** Theme tonal surface colors */
--clr-surface-tonal-a0: #201920;
--clr-surface-tonal-a10: #352e35;
--clr-surface-tonal-a20: #4b454b;
--clr-surface-tonal-a30: #625c62;
--clr-surface-tonal-a40: #7a757a;
--clr-surface-tonal-a50: #938f93;
/** Success colors */
--clr-success-a0: #22946e;
--clr-success-a10: #47d5a6;
--clr-success-a20: #9ae8ce;
/** Warning colors */
--clr-warning-a0: #a87a2a;
--clr-warning-a10: #d7ac61;
--clr-warning-a20: #ecd7b2;
/** Danger colors */
--clr-danger-a0: #9c2121;
--clr-danger-a10: #d94a4a;
--clr-danger-a20: #eb9e9e;
/** Info colors */
--clr-info-a0: #21498a;
--clr-info-a10: #4077d1;
--clr-info-a20: #92b2e5;
}
.background {
z-index: -1;
position: fixed;
top: 0;
animation: fadeIn 10s ease-in infinite;
min-width: 100vw;
min-height: 100vh;
object-fit: cover;
}
body {
background-color: #32004f;
margin: 0;
}
@keyframes fadeIn {
from {opacity: 0;}
50% {opacity: 1;}
to {opacity: 0;}
}

84
assets/css/index.css Normal file
View File

@@ -0,0 +1,84 @@
.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 {
display: flex;
flex-direction: column;
margin: 15vh auto 0 auto;
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 {
font-size: 68px;
font-family: 'Indie Flower', cursive;
color: var(--clr-primary-a50);
}
h2 {
font-size: 42px;
font-family: 'Indie Flower', cursive;
color: var(--clr-primary-a50);
}
@media screen and (width<700px) {
.intro {
margin: 5vh auto 0 auto;
}
h1 {
text-align: center;
margin-bottom: 8px;
}
h2 {
font-size: 28px;
}
}

BIN
assets/img/background.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

151
src/html/elements.rs Normal file
View File

@@ -0,0 +1,151 @@
use crate::html::Render;
#[allow(non_camel_case_types)]
pub(crate) struct h1 {
text: String,
}
impl Render for h1 {
fn render(&self) -> String {
format!("<h1>{}</h1>", self.text)
}
}
impl h1 {
pub(crate) fn new(text: String) -> Self {
Self { text }
}
}
#[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(text: String) -> Self {
Self { text }
}
}
#[allow(non_camel_case_types)]
pub(crate) struct link {
rel: String,
href: String,
}
impl Render for link {
fn render(&self) -> String {
format!("<link rel=\"{}\" href=\"{}\">", self.rel, self.href)
}
}
impl link {
pub(crate) fn new(rel: String, href: String) -> Self {
Self { rel, href }
}
}
#[allow(non_camel_case_types)]
pub(crate) struct div {
id: String,
classes: Vec<String>,
content: Vec<Box<dyn Render>>,
}
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(id: String, classes: Vec<String>) -> Self {
Self {
id,
classes,
content: vec![],
}
}
pub(crate) fn append_element(&mut self, element: impl Render + 'static) {
self.content.push(Box::new(element));
}
}
#[allow(non_camel_case_types)]
pub(crate) struct p {
id: String,
classes: Vec<String>,
text: String
}
impl Render for p {
fn render(&self) -> String {
let classes = self.classes.join(" ");
format!("<p id=\"{}\" class=\"{}\">{}</p>", self.id, classes, self.text)
}
}
impl p {
pub(crate) fn new(id: String, classes: Vec<String>, text: String) -> Self {
Self {
id,
classes,
text
}
}
}
#[allow(non_camel_case_types)]
pub(crate) struct img {
id: String,
classes: Vec<String>,
src: String,
}
impl Render for img {
fn render(&self) -> String {
let classes = self.classes.join(" ");
format!("<img id=\"{}\" class=\"{}\" src=\"{}\">", self.id, classes, self.src)
}
}
impl img {
pub(crate) fn new(id: String, classes: Vec<String>, src: String) -> Self {
Self { id, classes, src }
}
}
#[allow(non_camel_case_types)]
pub(crate) struct a {
id: String,
classes: Vec<String>,
href: String,
text: String,
}
impl Render for a {
fn render(&self) -> String {
let classes = self.classes.join(" ");
format!("<a id=\"{}\" class=\"{}\" href=\"{}\">{}</a>", self.id, classes, self.href, self.text)
}
}
impl a {
pub(crate) fn new(id: String, classes: Vec<String>, href: String, text: String) -> Self {
Self { id, classes, href, text }
}
}

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

@@ -0,0 +1,63 @@
pub(crate) mod elements;
pub(crate) trait Render {
fn render(&self) -> String;
}
pub(crate) struct Page {
title: String,
head: Vec<Box<dyn Render>>,
body: Vec<Box<dyn Render>>,
}
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
}
}
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 new(title: String) -> Self {
Page {
title,
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));
}
}

View File

@@ -1,3 +1,21 @@
fn main() { mod html;
println!("Hello, world!"); mod pages;
use actix_web::{App, HttpServer};
use pages::{files::file, index};
#[actix_web::main]
async fn main() {
let bind_address = "0.0.0.0:8080";
if let Ok(server) =
HttpServer::new(|| App::new().service(index).service(file)).bind(bind_address)
{
match server.run().await {
Ok(_) => {}
Err(e) => {
eprintln!("An error occurred: {}", e);
}
}
}
} }

19
src/pages/files.rs Normal file
View File

@@ -0,0 +1,19 @@
use actix_web::web::Path;
use actix_web::{HttpResponse, Responder, get};
use std::fs::File;
use std::io::Read;
use std::path::PathBuf;
#[get("/static/{file_type}/{file_name}")]
async fn file(file: Path<(String, String)>) -> impl Responder {
//TODO use assets dir
let mut path = PathBuf::from("assets");
path.push(&file.0.replace("/", ""));
path.push(&file.1.replace("/", ""));
let mut file = File::open(path).unwrap();
let mut bytes = Vec::new();
if file.read_to_end(&mut bytes).is_err() {
return HttpResponse::NotFound().body("File not found");
};
HttpResponse::Ok().body(bytes)
}

45
src/pages/mod.rs Normal file
View File

@@ -0,0 +1,45 @@
pub(crate) mod files;
use crate::html::elements::{a, div, h1, h2, img, link, p};
use crate::html::{Page, Render};
use actix_web::http::header::ContentType;
use actix_web::mime::TEXT_HTML;
use actix_web::{HttpResponse, Responder, get};
#[get("/")]
async fn index() -> impl Responder {
let mut page = Page::new("AINDUSTRIES".to_string());
// 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
let mut intro = div::new("intro".to_string(), vec!["intro".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>\
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());
let note = p::new("note".to_string(), vec!["note".to_string()], "Website in construction.".to_string());
intro.append_element(hi);
intro.append_element(detail);
intro.append_element(note);
// css
let base = link::new("stylesheet".to_string(), "static/css/base.css".to_string());
let index = link::new("stylesheet".to_string(), "static/css/index.css".to_string());
// add elements to the page in order
page.append_element_to_head(base);
page.append_element_to_head(index);
page.append_element_to_body(image);
page.append_element_to_body(header);
page.append_element_to_body(intro);
let response = HttpResponse::Ok()
.insert_header(ContentType(TEXT_HTML))
.body(page.render());
response
}