Compare commits
6 Commits
08b5932cf8
...
8c64c5dbe3
| Author | SHA1 | Date | |
|---|---|---|---|
| 8c64c5dbe3 | |||
| 88abbe8c14 | |||
| c5b85bc359 | |||
| 8a27a74e9f | |||
| 425bac6776 | |||
| 5c456fd5cd |
58
assets/css/music.css
Normal file
58
assets/css/music.css
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
.featured {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured h1 {
|
||||||
|
font-size: 48px;
|
||||||
|
text-align: center;
|
||||||
|
font-family: 'Indie Flower', cursive;
|
||||||
|
color: var(--clr-primary-a60);
|
||||||
|
max-width: 75vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured iframe {
|
||||||
|
width: 55vw;
|
||||||
|
height: 100%;
|
||||||
|
aspect-ratio: 16 / 9;
|
||||||
|
border: none;
|
||||||
|
filter: drop-shadow(0px 0px 12px rgba(0, 0, 0, 0.75));
|
||||||
|
}
|
||||||
|
|
||||||
|
.playlist {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playlist h1 {
|
||||||
|
font-size: 40px;
|
||||||
|
text-align: center;
|
||||||
|
font-family: 'Indie Flower', cursive;
|
||||||
|
color: var(--clr-primary-a60);
|
||||||
|
max-width: 75vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.playlist iframe {
|
||||||
|
width: 55vw;
|
||||||
|
height: 100%;
|
||||||
|
aspect-ratio: 16 / 9;
|
||||||
|
border: none;
|
||||||
|
filter: drop-shadow(0px 0px 12px rgba(0, 0, 0, 0.75));
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (width <= 1250px) {
|
||||||
|
.featured iframe {
|
||||||
|
width: 90vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playlist iframe {
|
||||||
|
width: 90vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -390,3 +390,115 @@ impl AnchorBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) struct Iframe {
|
||||||
|
id: String,
|
||||||
|
classes: Vec<String>,
|
||||||
|
src: String,
|
||||||
|
title: String,
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for Iframe {
|
||||||
|
fn render(&self) -> String {
|
||||||
|
let classes = self.classes.join(" ");
|
||||||
|
format!(
|
||||||
|
"<iframe id=\"{}\" classes=\"{}\" title=\"{}\" width=\"{}\" height=\"{}\" src=\"{}\"></iframe>",
|
||||||
|
self.id, classes, self.title, self.width, self.height, self.src
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct IframeBuilder {
|
||||||
|
id: Option<String>,
|
||||||
|
classes: Option<Vec<String>>,
|
||||||
|
src: Option<String>,
|
||||||
|
title: Option<String>,
|
||||||
|
width: Option<usize>,
|
||||||
|
height: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IframeBuilder {
|
||||||
|
pub(crate) fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
id: None,
|
||||||
|
classes: None,
|
||||||
|
src: None,
|
||||||
|
title: None,
|
||||||
|
width: None,
|
||||||
|
height: 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 title<T>(self, title: T) -> Self
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
title: Some(title.into()),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn width(self, width: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
width: Some(width),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn height(self, height: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
height: Some(height),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn build(self) -> Iframe {
|
||||||
|
let id = self.id.unwrap_or(String::new());
|
||||||
|
let classes = self.classes.unwrap_or(Vec::new());
|
||||||
|
let title = self.title.unwrap_or(String::new());
|
||||||
|
let width = self.width.unwrap_or(0);
|
||||||
|
let height = self.height.unwrap_or(0);
|
||||||
|
let src = self.src.unwrap_or(String::new());
|
||||||
|
Iframe {
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
classes,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
src,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -175,9 +175,15 @@ impl BasePageBuilder {
|
|||||||
.href("/projects")
|
.href("/projects")
|
||||||
.text("Projects")
|
.text("Projects")
|
||||||
.build();
|
.build();
|
||||||
|
let music = Anchor::builder()
|
||||||
|
.id("music")
|
||||||
|
.classes(vec!["nav-button"])
|
||||||
|
.href("/music")
|
||||||
|
.text("Music")
|
||||||
|
.build();
|
||||||
let buttons = Division::builder()
|
let buttons = Division::builder()
|
||||||
.classes(vec!["nav-buttons"])
|
.classes(vec!["nav-buttons"])
|
||||||
.elements(boxed_vec![home, projects])
|
.elements(boxed_vec![home, projects, music])
|
||||||
.build();
|
.build();
|
||||||
let header = Division::builder()
|
let header = Division::builder()
|
||||||
.id("header")
|
.id("header")
|
||||||
|
|||||||
22
src/main.rs
22
src/main.rs
@@ -11,13 +11,14 @@ use std::io::Read;
|
|||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use toml::from_slice;
|
use toml::from_slice;
|
||||||
|
|
||||||
|
use crate::pages::files::robots;
|
||||||
use middleware::MimeType;
|
use middleware::MimeType;
|
||||||
use pages::{
|
use pages::{
|
||||||
files::file,
|
files::file,
|
||||||
index,
|
index,
|
||||||
|
music::music,
|
||||||
projects::{project, projects},
|
projects::{project, projects},
|
||||||
};
|
};
|
||||||
use crate::pages::files::robots;
|
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(name = "aindustries-be", version, about = "This is the amazing webserver for aindustries.be!", long_about = None)]
|
#[command(name = "aindustries-be", version, about = "This is the amazing webserver for aindustries.be!", long_about = None)]
|
||||||
@@ -100,13 +101,18 @@ async fn main() {
|
|||||||
let assets = assets.replace("~", "/home/conta/Code/aindustries.be/assets");
|
let assets = assets.replace("~", "/home/conta/Code/aindustries.be/assets");
|
||||||
let data = web::Data::new(AppState { assets, pool });
|
let data = web::Data::new(AppState { assets, pool });
|
||||||
if let Ok(server) = HttpServer::new(move || {
|
if let Ok(server) = HttpServer::new(move || {
|
||||||
App::new().app_data(data.clone()).service(file).service(robots).service(
|
App::new()
|
||||||
web::scope("")
|
.app_data(data.clone())
|
||||||
.wrap(MimeType::new("text/html"))
|
.service(file)
|
||||||
.service(index)
|
.service(robots)
|
||||||
.service(projects)
|
.service(
|
||||||
.service(project),
|
web::scope("")
|
||||||
)
|
.wrap(MimeType::new("text/html"))
|
||||||
|
.service(index)
|
||||||
|
.service(projects)
|
||||||
|
.service(project)
|
||||||
|
.service(music),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.bind(bind_address)
|
.bind(bind_address)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
pub(crate) mod files;
|
pub(crate) mod files;
|
||||||
|
pub(crate) mod music;
|
||||||
pub(crate) mod projects;
|
pub(crate) mod projects;
|
||||||
|
|
||||||
use crate::html::elements::{Heading, Link};
|
use crate::html::elements::{Heading, Link};
|
||||||
|
|||||||
54
src/pages/music.rs
Normal file
54
src/pages/music.rs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
use crate::html::layouts::DivisionBuilder;
|
||||||
|
use crate::html::pages::BasePageBuilder;
|
||||||
|
use crate::html::{Render, boxed_vec};
|
||||||
|
|
||||||
|
use crate::AppState;
|
||||||
|
use crate::html::elements::{HeadingBuilder, IframeBuilder, Link};
|
||||||
|
use actix_web::web::Data;
|
||||||
|
use actix_web::{Responder, get, web};
|
||||||
|
use sqlx::query_as;
|
||||||
|
|
||||||
|
#[get("/music")]
|
||||||
|
async fn music(state: Data<AppState>) -> impl Responder {
|
||||||
|
let featured_title = HeadingBuilder::new()
|
||||||
|
.level(1)
|
||||||
|
.text("The song I've been listening to the most recently is \"Hot\" by Avril Lavigne.")
|
||||||
|
.build();
|
||||||
|
let featured_iframe = IframeBuilder::new()
|
||||||
|
.src("https://www.youtube.com/embed/fzb75m8NuMQ")
|
||||||
|
.build();
|
||||||
|
let featured = DivisionBuilder::new()
|
||||||
|
.classes(vec!["featured"])
|
||||||
|
.id("featured")
|
||||||
|
.elements(boxed_vec![featured_title, featured_iframe])
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let playlist_title = HeadingBuilder::new()
|
||||||
|
.text(
|
||||||
|
"You can find below my primary playlist, \
|
||||||
|
the one containing almost all the songs I have been listening to throughout the years.",
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
let playlist_iframe = IframeBuilder::new()
|
||||||
|
.src(
|
||||||
|
"https://open.spotify.com/embed/playlist/\
|
||||||
|
3owjyc1RILDGkgCmdCal39?utm_source=generator",
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
let playlist = DivisionBuilder::new()
|
||||||
|
.id("playlist")
|
||||||
|
.classes(vec!["playlist"])
|
||||||
|
.elements(boxed_vec![playlist_title, playlist_iframe])
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let css = Link::builder()
|
||||||
|
.rel("stylesheet")
|
||||||
|
.href("/static/css/music.css")
|
||||||
|
.build();
|
||||||
|
let page = BasePageBuilder::new()
|
||||||
|
.title("Music")
|
||||||
|
.head(boxed_vec![css])
|
||||||
|
.body(boxed_vec![featured, playlist])
|
||||||
|
.build();
|
||||||
|
page.render()
|
||||||
|
}
|
||||||
@@ -1,18 +1,18 @@
|
|||||||
|
use crate::AppState;
|
||||||
use crate::html::elements::{Anchor, Heading, Link, Paragraph};
|
use crate::html::elements::{Anchor, Heading, Link, Paragraph};
|
||||||
use crate::html::layouts::Division;
|
use crate::html::layouts::Division;
|
||||||
use crate::html::pages::BasePage;
|
use crate::html::pages::BasePage;
|
||||||
use crate::html::{Render, boxed_vec};
|
use crate::html::{Render, boxed_vec};
|
||||||
use actix_web::{Responder, get, web};
|
|
||||||
use actix_web::web::Data;
|
use actix_web::web::Data;
|
||||||
|
use actix_web::{Responder, get, web};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sqlx::query_as;
|
use sqlx::query_as;
|
||||||
use crate::AppState;
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct Project {
|
struct Project {
|
||||||
id: String,
|
id: String,
|
||||||
title: String,
|
title: String,
|
||||||
description: String
|
description: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/projects")]
|
#[get("/projects")]
|
||||||
@@ -25,23 +25,40 @@ async fn projects(app_state: Data<AppState>) -> impl Responder {
|
|||||||
(I've done a lot of small projects but they are not worth it.)",
|
(I've done a lot of small projects but they are not worth it.)",
|
||||||
)
|
)
|
||||||
.build();
|
.build();
|
||||||
let projects = match query_as!(Project, "SELECT id, title, description FROM Projects").fetch_all(&app_state.pool).await {
|
let projects = match query_as!(Project, "SELECT id, title, description FROM Projects")
|
||||||
|
.fetch_all(&app_state.pool)
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(projects) => projects,
|
Ok(projects) => projects,
|
||||||
Err(_) => {vec![]}
|
Err(_) => {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let items: Vec<Box<dyn Render>> = projects.into_iter().map(|p| {
|
let items: Vec<Box<dyn Render>> = projects
|
||||||
let title = Heading::builder().text(p.title).build();
|
.into_iter()
|
||||||
let description = Paragraph::builder().classes(vec!["project-desc"]).text(p.description).build();
|
.map(|p| {
|
||||||
let view = Anchor::builder().classes(vec!["project-view"]).href(format!("/projects/{}", p.id)).text("Learn More").build();
|
let title = Heading::builder().text(p.title).build();
|
||||||
let info = Division::builder()
|
let description = Paragraph::builder()
|
||||||
.classes(vec!["project-info"])
|
.classes(vec!["project-desc"])
|
||||||
.elements(boxed_vec![description, view])
|
.text(p.description)
|
||||||
.build();
|
.build();
|
||||||
Box::new(Division::builder()
|
let view = Anchor::builder()
|
||||||
.classes(vec!["project"])
|
.classes(vec!["project-view"])
|
||||||
.elements(boxed_vec![title, info])
|
.href(format!("/projects/{}", p.id))
|
||||||
.build()) as Box<dyn Render>
|
.text("Learn More")
|
||||||
}).collect();
|
.build();
|
||||||
|
let info = Division::builder()
|
||||||
|
.classes(vec!["project-info"])
|
||||||
|
.elements(boxed_vec![description, view])
|
||||||
|
.build();
|
||||||
|
Box::new(
|
||||||
|
Division::builder()
|
||||||
|
.classes(vec!["project"])
|
||||||
|
.elements(boxed_vec![title, info])
|
||||||
|
.build(),
|
||||||
|
) as Box<dyn Render>
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
let css = Link::builder()
|
let css = Link::builder()
|
||||||
.rel("stylesheet")
|
.rel("stylesheet")
|
||||||
.href("/static/css/projects.css")
|
.href("/static/css/projects.css")
|
||||||
@@ -62,7 +79,7 @@ struct ProjectDetail {
|
|||||||
title: String,
|
title: String,
|
||||||
git_url: String,
|
git_url: String,
|
||||||
git_title: String,
|
git_title: String,
|
||||||
description: String
|
description: String,
|
||||||
}
|
}
|
||||||
#[get("/projects/{project}")]
|
#[get("/projects/{project}")]
|
||||||
async fn project(project: web::Path<String>, app_state: Data<AppState>) -> impl Responder {
|
async fn project(project: web::Path<String>, app_state: Data<AppState>) -> impl Responder {
|
||||||
@@ -75,12 +92,19 @@ async fn project(project: web::Path<String>, app_state: Data<AppState>) -> impl
|
|||||||
.title(format!("Project-{}", project))
|
.title(format!("Project-{}", project))
|
||||||
.head(boxed_vec![css])
|
.head(boxed_vec![css])
|
||||||
.build();
|
.build();
|
||||||
let project = match query_as!(ProjectDetail, "SELECT * FROM Project_Detail WHERE id = $1", project).fetch_one(&app_state.pool).await {
|
let project = match query_as!(
|
||||||
|
ProjectDetail,
|
||||||
|
"SELECT * FROM Project_Detail WHERE id = $1",
|
||||||
|
project
|
||||||
|
)
|
||||||
|
.fetch_one(&app_state.pool)
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(project) => project,
|
Ok(project) => project,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
// return not found page
|
// return not found page
|
||||||
eprintln!("Error fetching project: {:?}", error);
|
eprintln!("Error fetching project: {:?}", error);
|
||||||
return page.render()
|
return page.render();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let title = Heading::builder().text(project.title).build();
|
let title = Heading::builder().text(project.title).build();
|
||||||
@@ -88,9 +112,14 @@ async fn project(project: web::Path<String>, app_state: Data<AppState>) -> impl
|
|||||||
.href(project.git_url)
|
.href(project.git_url)
|
||||||
.text(project.git_title)
|
.text(project.git_title)
|
||||||
.build();
|
.build();
|
||||||
let desc = Paragraph::builder().classes(vec!["description"]).text(
|
let desc = Paragraph::builder()
|
||||||
format!("{}<br><br> Wish to see more? Check out the gitea repository: {}", project.description, gitea.render()),
|
.classes(vec!["description"])
|
||||||
).build();
|
.text(format!(
|
||||||
|
"{}<br><br> Wish to see more? Check out the gitea repository: {}",
|
||||||
|
project.description,
|
||||||
|
gitea.render()
|
||||||
|
))
|
||||||
|
.build();
|
||||||
page.append_element_to_body(title);
|
page.append_element_to_body(title);
|
||||||
page.append_element_to_body(desc);
|
page.append_element_to_body(desc);
|
||||||
page.render()
|
page.render()
|
||||||
|
|||||||
Reference in New Issue
Block a user