Compare commits
5 Commits
cab1c45385
...
cba44c144d
| Author | SHA1 | Date | |
|---|---|---|---|
| cba44c144d | |||
| 71911f2662 | |||
| 4b10832591 | |||
| af6fb8fd68 | |||
| 59305ce83c |
@@ -4,3 +4,7 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[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
71
assets/css/base.css
Normal 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
84
assets/css/index.css
Normal 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
BIN
assets/img/background.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 156 KiB |
151
src/html/elements.rs
Normal file
151
src/html/elements.rs
Normal 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
63
src/html/mod.rs
Normal 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));
|
||||
}
|
||||
}
|
||||
22
src/main.rs
22
src/main.rs
@@ -1,3 +1,21 @@
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
mod html;
|
||||
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
19
src/pages/files.rs
Normal 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
45
src/pages/mod.rs
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user