This commit is contained in:
parent
4f09373df3
commit
2979e21285
@ -28,7 +28,7 @@ This week I've attended a [Rusty game jam #2](https://itch.io/jam/rusty-jam-2).
|
|||||||
|
|
||||||
![Egg fetcher game preview](/images/uploads/screenshot-from-2022-06-26-22-37-16.png "Egg fetcher game preview")
|
![Egg fetcher game preview](/images/uploads/screenshot-from-2022-06-26-22-37-16.png "Egg fetcher game preview")
|
||||||
|
|
||||||
[You can check the result built with WASM here.](/showcase/egg-fetcher/)
|
[You can check the result built with WASM here.](/showcase/egg-fetcher)
|
||||||
|
|
||||||
## What's up with the weeklys
|
## What's up with the weeklys
|
||||||
|
|
||||||
|
2
justfile
2
justfile
@ -30,7 +30,7 @@ test_watch:
|
|||||||
cargo watch -x test
|
cargo watch -x test
|
||||||
|
|
||||||
# Run server in production mode
|
# Run server in production mode
|
||||||
prod $TARGET="PROD":
|
prod $TARGET="PROD" $RUST_LOG="info":
|
||||||
cargo run --release
|
cargo run --release
|
||||||
|
|
||||||
# Wait for port to listen to connections
|
# Wait for port to listen to connections
|
||||||
|
2
robots.txt
Normal file
2
robots.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
User-agent: *
|
||||||
|
Disallow: /admin
|
@ -4,13 +4,19 @@ use tracing::debug;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
blog_posts::blog_post_model::{BlogPostMetadata, BLOG_POST_PATH},
|
blog_posts::blog_post_model::{BlogPostMetadata, BLOG_POST_PATH},
|
||||||
post_utils::post_listing::get_post_list,
|
post_utils::{post_listing::get_post_list, post_parser::ParseResult},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn get_popular_blog_tags() -> Result<Vec<String>, StatusCode> {
|
pub async fn get_popular_tags(segment: Option<String>) -> Result<Vec<String>, StatusCode> {
|
||||||
const TAGS_LENGTH: usize = 7;
|
const TAGS_LENGTH: usize = 7;
|
||||||
|
|
||||||
let post_list = get_post_list::<BlogPostMetadata>(BLOG_POST_PATH).await?;
|
let mut post_list = get_post_list::<BlogPostMetadata>(BLOG_POST_PATH).await?;
|
||||||
|
post_list.retain(|post| post.metadata.published);
|
||||||
|
|
||||||
|
if let Some(segment) = segment {
|
||||||
|
post_list.retain(|post| post.metadata.segments.contains(&segment));
|
||||||
|
}
|
||||||
|
|
||||||
let tags_sum = post_list
|
let tags_sum = post_list
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|post| post.metadata.tags)
|
.flat_map(|post| post.metadata.tags)
|
||||||
@ -27,12 +33,32 @@ pub async fn get_popular_blog_tags() -> Result<Vec<String>, StatusCode> {
|
|||||||
debug!("Tag: {}, Count: {}", tag, count);
|
debug!("Tag: {}, Count: {}", tag, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
let popular_blog_tags = sorted_tags_by_count
|
let popular_tags = sorted_tags_by_count
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|tag_count| tag_count.0)
|
.map(|tag_count| tag_count.0)
|
||||||
.filter(|tag| tag != "dev")
|
.filter(|tag| tag != "dev")
|
||||||
.take(TAGS_LENGTH)
|
.take(TAGS_LENGTH)
|
||||||
.collect::<Vec<String>>();
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
Ok(popular_blog_tags)
|
Ok(popular_tags)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_posts_by_tag(
|
||||||
|
post_list: Vec<ParseResult<BlogPostMetadata>>,
|
||||||
|
tag: &Option<String>,
|
||||||
|
) -> Vec<ParseResult<BlogPostMetadata>> {
|
||||||
|
match tag {
|
||||||
|
Some(tag) => post_list
|
||||||
|
.into_iter()
|
||||||
|
.filter(|post| {
|
||||||
|
post.metadata
|
||||||
|
.tags
|
||||||
|
.iter()
|
||||||
|
.map(|post_tag| post_tag.to_lowercase())
|
||||||
|
.collect::<String>()
|
||||||
|
.contains(&tag.to_lowercase())
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
None => post_list,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,5 +49,5 @@ pub async fn render_rss_feed() -> Result<impl IntoResponse, StatusCode> {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
let response = feed_builder.to_string();
|
let response = feed_builder.to_string();
|
||||||
return Ok(([(header::CONTENT_TYPE, "application/xml")], response));
|
Ok(([(header::CONTENT_TYPE, "application/xml")], response))
|
||||||
}
|
}
|
||||||
|
20
src/main.rs
20
src/main.rs
@ -1,4 +1,5 @@
|
|||||||
use axum::{self};
|
use askama_axum::IntoResponse;
|
||||||
|
use axum::{self, extract::OriginalUri, http::StatusCode};
|
||||||
use tower_http::services::ServeDir;
|
use tower_http::services::ServeDir;
|
||||||
use tower_livereload::LiveReloadLayer;
|
use tower_livereload::LiveReloadLayer;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
@ -33,9 +34,14 @@ async fn main() {
|
|||||||
.nest_service("/styles", ServeDir::new("styles"))
|
.nest_service("/styles", ServeDir::new("styles"))
|
||||||
.nest_service("/images", ServeDir::new("static/images"))
|
.nest_service("/images", ServeDir::new("static/images"))
|
||||||
.nest_service("/fonts", ServeDir::new("static/fonts"))
|
.nest_service("/fonts", ServeDir::new("static/fonts"))
|
||||||
|
.nest_service("/files", ServeDir::new("static/files"))
|
||||||
.nest_service("/generated_images", ServeDir::new("generated_images"))
|
.nest_service("/generated_images", ServeDir::new("generated_images"))
|
||||||
|
.nest_service("/egg-fetcher", ServeDir::new("static/egg-fetcher"))
|
||||||
.nest_service("/svg", ServeDir::new("static/svg"))
|
.nest_service("/svg", ServeDir::new("static/svg"))
|
||||||
.nest_service("/config.yml", ServeDir::new("static/resources/config.yml")); // Decap CMS config
|
.nest_service("/config.yml", ServeDir::new("static/resources/config.yml")) // Decap CMS config
|
||||||
|
.nest_service("/robots.txt", ServeDir::new("robots.txt"));
|
||||||
|
|
||||||
|
let app = app.fallback(handler_404);
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
let app = app.layer(LiveReloadLayer::new());
|
let app = app.layer(LiveReloadLayer::new());
|
||||||
@ -48,6 +54,11 @@ async fn main() {
|
|||||||
axum::serve(listener, app).await.unwrap();
|
axum::serve(listener, app).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handler_404(OriginalUri(original_uri): OriginalUri) -> impl IntoResponse {
|
||||||
|
info!("{original_uri} not found");
|
||||||
|
(StatusCode::NOT_FOUND, "nothing to see here")
|
||||||
|
}
|
||||||
|
|
||||||
// TODO Socials
|
// TODO Socials
|
||||||
// - fotos
|
// - fotos
|
||||||
// background gradient color
|
// background gradient color
|
||||||
@ -56,3 +67,8 @@ async fn main() {
|
|||||||
// TODO after release
|
// TODO after release
|
||||||
// OG tags
|
// OG tags
|
||||||
// - projects page
|
// - projects page
|
||||||
|
// TODO broken links
|
||||||
|
// showcase/eggfetcher
|
||||||
|
// broadcasts/
|
||||||
|
// manifest.json
|
||||||
|
//
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use askama::Template;
|
use askama::Template;
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{OriginalUri, Path, RawQuery},
|
extract::{OriginalUri, Path},
|
||||||
http::StatusCode,
|
http::StatusCode,
|
||||||
};
|
};
|
||||||
use tokio::try_join;
|
use tokio::try_join;
|
||||||
@ -9,7 +9,7 @@ use tracing::debug;
|
|||||||
use crate::{
|
use crate::{
|
||||||
blog_posts::{
|
blog_posts::{
|
||||||
blog_post_model::{BlogPostMetadata, BLOG_POST_PATH},
|
blog_post_model::{BlogPostMetadata, BLOG_POST_PATH},
|
||||||
tag_list::get_popular_blog_tags,
|
tag_list::{get_popular_tags, get_posts_by_tag},
|
||||||
},
|
},
|
||||||
components::site_header::{HeaderProps, Link},
|
components::site_header::{HeaderProps, Link},
|
||||||
filters,
|
filters,
|
||||||
@ -21,10 +21,11 @@ use crate::{
|
|||||||
#[template(path = "blog_post_list.html")]
|
#[template(path = "blog_post_list.html")]
|
||||||
pub struct PostListTemplate {
|
pub struct PostListTemplate {
|
||||||
pub title: String,
|
pub title: String,
|
||||||
|
pub og_title: String,
|
||||||
|
pub segment: String,
|
||||||
pub posts: Vec<ParseResult<BlogPostMetadata>>,
|
pub posts: Vec<ParseResult<BlogPostMetadata>>,
|
||||||
pub tag: Option<String>,
|
|
||||||
pub header_props: HeaderProps,
|
pub header_props: HeaderProps,
|
||||||
pub blog_tags: Vec<String>,
|
pub tags: Vec<String>,
|
||||||
pub featured_projects: Vec<ParseResult<ProjectMetadata>>,
|
pub featured_projects: Vec<ParseResult<ProjectMetadata>>,
|
||||||
pub current_url: String,
|
pub current_url: String,
|
||||||
}
|
}
|
||||||
@ -37,30 +38,17 @@ pub async fn render_blog_post_list(
|
|||||||
let tag = tag.map(|Path(tag)| tag);
|
let tag = tag.map(|Path(tag)| tag);
|
||||||
|
|
||||||
let (blog_tags, featured_projects, mut post_list) = try_join!(
|
let (blog_tags, featured_projects, mut post_list) = try_join!(
|
||||||
get_popular_blog_tags(),
|
get_popular_tags(Some("blog".to_string())),
|
||||||
get_featured_projects(),
|
get_featured_projects(),
|
||||||
get_post_list::<BlogPostMetadata>(BLOG_POST_PATH)
|
get_post_list::<BlogPostMetadata>(BLOG_POST_PATH)
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
post_list.sort_by_key(|post| post.metadata.date);
|
post_list.sort_by_key(|post| post.metadata.date);
|
||||||
post_list.retain(|post| post.metadata.published);
|
post_list.retain(|post| post.metadata.published);
|
||||||
|
post_list.retain(|post| post.metadata.segments.contains(&"blog".to_string()));
|
||||||
post_list.reverse();
|
post_list.reverse();
|
||||||
|
|
||||||
let posts = match &tag {
|
let posts = get_posts_by_tag(post_list, &tag);
|
||||||
Some(tag) => post_list
|
|
||||||
.into_iter()
|
|
||||||
.filter(|post| {
|
|
||||||
post.metadata
|
|
||||||
.tags
|
|
||||||
.iter()
|
|
||||||
.map(|post_tag| post_tag.to_lowercase())
|
|
||||||
.collect::<String>()
|
|
||||||
.contains(&tag.to_lowercase())
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
None => post_list,
|
|
||||||
};
|
|
||||||
|
|
||||||
let header_props = match tag {
|
let header_props = match tag {
|
||||||
Some(_) => HeaderProps::with_back_link(Link {
|
Some(_) => HeaderProps::with_back_link(Link {
|
||||||
href: "/blog".to_string(),
|
href: "/blog".to_string(),
|
||||||
@ -71,18 +59,19 @@ pub async fn render_blog_post_list(
|
|||||||
|
|
||||||
debug!("uri:{:?}", original_uri);
|
debug!("uri:{:?}", original_uri);
|
||||||
|
|
||||||
let title = if let Some(tag) = &tag {
|
let (title, og_title) = if let Some(tag) = &tag {
|
||||||
format!("{tag} blog posts")
|
(format!("#{tag}"), format!("{tag} blog posts"))
|
||||||
} else {
|
} else {
|
||||||
"Blog posts".to_string()
|
("Blog posts".to_string(), "Blog posts".to_string())
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(PostListTemplate {
|
Ok(PostListTemplate {
|
||||||
title,
|
title,
|
||||||
|
og_title,
|
||||||
|
segment: "blog".to_string(),
|
||||||
posts,
|
posts,
|
||||||
tag,
|
|
||||||
header_props,
|
header_props,
|
||||||
blog_tags,
|
tags: blog_tags,
|
||||||
featured_projects,
|
featured_projects,
|
||||||
current_url: original_uri.to_string(),
|
current_url: original_uri.to_string(),
|
||||||
})
|
})
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use askama::Template;
|
use askama::Template;
|
||||||
|
use axum::extract::OriginalUri;
|
||||||
use axum::{extract::Path, http::StatusCode};
|
use axum::{extract::Path, http::StatusCode};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
|
||||||
@ -17,15 +18,38 @@ pub struct BlogPostTemplate {
|
|||||||
pub body: String,
|
pub body: String,
|
||||||
pub date: DateTime<Utc>,
|
pub date: DateTime<Utc>,
|
||||||
pub tags: Vec<String>,
|
pub tags: Vec<String>,
|
||||||
|
pub segment: String,
|
||||||
pub header_props: HeaderProps,
|
pub header_props: HeaderProps,
|
||||||
pub slug: String,
|
pub slug: String,
|
||||||
pub thumbnail: Option<String>,
|
pub thumbnail: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn render_blog_post(Path(post_id): Path<String>) -> Result<BlogPostTemplate, StatusCode> {
|
pub async fn render_blog_post(
|
||||||
|
Path(post_id): Path<String>,
|
||||||
|
OriginalUri(original_uri): OriginalUri,
|
||||||
|
) -> Result<BlogPostTemplate, StatusCode> {
|
||||||
let path = format!("{}/{}.md", BLOG_POST_PATH, post_id);
|
let path = format!("{}/{}.md", BLOG_POST_PATH, post_id);
|
||||||
let parse_post = parse_post::<BlogPostMetadata>(&path, true);
|
let parse_post = parse_post::<BlogPostMetadata>(&path, true);
|
||||||
let parsed = parse_post.await?;
|
let parsed = parse_post.await?;
|
||||||
|
let segment = if original_uri.to_string().starts_with("/blog") {
|
||||||
|
"blog"
|
||||||
|
} else if original_uri.to_string().starts_with("/broadcasts") {
|
||||||
|
"broadcasts"
|
||||||
|
} else {
|
||||||
|
"blog"
|
||||||
|
};
|
||||||
|
|
||||||
|
let header_props = match segment {
|
||||||
|
"blog" => HeaderProps::with_back_link(Link {
|
||||||
|
href: "/blog".to_string(),
|
||||||
|
label: "All posts".to_string(),
|
||||||
|
}),
|
||||||
|
"broadcasts" => HeaderProps::with_back_link(Link {
|
||||||
|
href: "/broadcasts".to_string(),
|
||||||
|
label: "All broadcasts".to_string(),
|
||||||
|
}),
|
||||||
|
_ => HeaderProps::default(),
|
||||||
|
};
|
||||||
|
|
||||||
Ok(BlogPostTemplate {
|
Ok(BlogPostTemplate {
|
||||||
title: parsed.metadata.title,
|
title: parsed.metadata.title,
|
||||||
@ -33,10 +57,8 @@ pub async fn render_blog_post(Path(post_id): Path<String>) -> Result<BlogPostTem
|
|||||||
tags: parsed.metadata.tags,
|
tags: parsed.metadata.tags,
|
||||||
body: parsed.body,
|
body: parsed.body,
|
||||||
slug: parsed.slug,
|
slug: parsed.slug,
|
||||||
|
segment: segment.to_string(),
|
||||||
thumbnail: parsed.metadata.thumbnail,
|
thumbnail: parsed.metadata.thumbnail,
|
||||||
header_props: HeaderProps::with_back_link(Link {
|
header_props,
|
||||||
href: "/blog".to_string(),
|
|
||||||
label: "All posts".to_string(),
|
|
||||||
}),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
66
src/pages/broadcast_list.rs
Normal file
66
src/pages/broadcast_list.rs
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
use axum::{
|
||||||
|
extract::{OriginalUri, Path},
|
||||||
|
http::StatusCode,
|
||||||
|
};
|
||||||
|
use tokio::try_join;
|
||||||
|
use tracing::debug;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
blog_posts::{
|
||||||
|
blog_post_model::{BlogPostMetadata, BLOG_POST_PATH},
|
||||||
|
tag_list::{get_popular_tags, get_posts_by_tag},
|
||||||
|
},
|
||||||
|
components::site_header::{HeaderProps, Link},
|
||||||
|
post_utils::post_listing::get_post_list,
|
||||||
|
projects::featured_projects::get_featured_projects,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::blog_post_list::PostListTemplate;
|
||||||
|
|
||||||
|
pub async fn render_broadcast_post_list(
|
||||||
|
tag: Option<Path<String>>,
|
||||||
|
OriginalUri(original_uri): OriginalUri,
|
||||||
|
) -> Result<PostListTemplate, StatusCode> {
|
||||||
|
// I will forget what happens here in a week. But essentially it's pattern matching and shadowing
|
||||||
|
let tag = tag.map(|Path(tag)| tag);
|
||||||
|
|
||||||
|
let (popular_tags, featured_projects, mut post_list) = try_join!(
|
||||||
|
get_popular_tags(Some("broadcasts".to_string())),
|
||||||
|
get_featured_projects(),
|
||||||
|
get_post_list::<BlogPostMetadata>(BLOG_POST_PATH)
|
||||||
|
)?;
|
||||||
|
|
||||||
|
post_list.sort_by_key(|post| post.metadata.date);
|
||||||
|
post_list.retain(|post| post.metadata.published);
|
||||||
|
post_list.retain(|post| post.metadata.segments.contains(&"broadcasts".to_string()));
|
||||||
|
post_list.reverse();
|
||||||
|
|
||||||
|
let posts = get_posts_by_tag(post_list, &tag);
|
||||||
|
|
||||||
|
let header_props = match tag {
|
||||||
|
Some(_) => HeaderProps::with_back_link(Link {
|
||||||
|
href: "/broadcasts".to_string(),
|
||||||
|
label: "All broadcasts".to_string(),
|
||||||
|
}),
|
||||||
|
None => HeaderProps::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
debug!("uri:{:?}", original_uri);
|
||||||
|
|
||||||
|
let title = if let Some(tag) = &tag {
|
||||||
|
format!("#{tag} broadcasts")
|
||||||
|
} else {
|
||||||
|
"Broadcasts".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(PostListTemplate {
|
||||||
|
title: title.clone(),
|
||||||
|
og_title: title,
|
||||||
|
segment: "broadcasts".to_string(),
|
||||||
|
posts,
|
||||||
|
header_props,
|
||||||
|
tags: popular_tags,
|
||||||
|
featured_projects,
|
||||||
|
current_url: original_uri.to_string(),
|
||||||
|
})
|
||||||
|
}
|
@ -5,7 +5,7 @@ use tokio::try_join;
|
|||||||
use crate::{
|
use crate::{
|
||||||
blog_posts::{
|
blog_posts::{
|
||||||
blog_post_model::BlogPostMetadata, featured_blog_posts::get_featured_blog_posts,
|
blog_post_model::BlogPostMetadata, featured_blog_posts::get_featured_blog_posts,
|
||||||
tag_list::get_popular_blog_tags,
|
tag_list::get_popular_tags,
|
||||||
},
|
},
|
||||||
components::site_header::HeaderProps,
|
components::site_header::HeaderProps,
|
||||||
filters,
|
filters,
|
||||||
@ -24,7 +24,7 @@ pub struct IndexTemplate {
|
|||||||
|
|
||||||
pub async fn render_index() -> Result<IndexTemplate, StatusCode> {
|
pub async fn render_index() -> Result<IndexTemplate, StatusCode> {
|
||||||
let (blog_tags, featured_blog_posts, featured_projects) = try_join!(
|
let (blog_tags, featured_blog_posts, featured_projects) = try_join!(
|
||||||
get_popular_blog_tags(),
|
get_popular_tags(Some("blog".to_string())),
|
||||||
get_featured_blog_posts(),
|
get_featured_blog_posts(),
|
||||||
get_featured_projects()
|
get_featured_projects()
|
||||||
)?;
|
)?;
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
pub mod admin;
|
pub mod admin;
|
||||||
pub mod blog_post_list;
|
pub mod blog_post_list;
|
||||||
pub mod blog_post_page;
|
pub mod blog_post_page;
|
||||||
|
pub mod broadcast_list;
|
||||||
pub mod contact;
|
pub mod contact;
|
||||||
pub mod index;
|
pub mod index;
|
||||||
pub mod project_list;
|
pub mod project_list;
|
||||||
|
pub mod showcase;
|
||||||
|
16
src/pages/showcase/egg_fetcher.rs
Normal file
16
src/pages/showcase/egg_fetcher.rs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
use askama::Template;
|
||||||
|
use axum::http::StatusCode;
|
||||||
|
|
||||||
|
use crate::components::site_header::HeaderProps;
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "egg_fetcher_page.html")]
|
||||||
|
pub struct EggFetcherShowcaseTemplate {
|
||||||
|
header_props: HeaderProps,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn render_egg_fetcher() -> Result<EggFetcherShowcaseTemplate, StatusCode> {
|
||||||
|
Ok(EggFetcherShowcaseTemplate {
|
||||||
|
header_props: HeaderProps::default(),
|
||||||
|
})
|
||||||
|
}
|
1
src/pages/showcase/mod.rs
Normal file
1
src/pages/showcase/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub mod egg_fetcher;
|
@ -2,8 +2,9 @@ use crate::{
|
|||||||
feed::render_rss_feed,
|
feed::render_rss_feed,
|
||||||
pages::{
|
pages::{
|
||||||
admin::render_admin, blog_post_list::render_blog_post_list,
|
admin::render_admin, blog_post_list::render_blog_post_list,
|
||||||
blog_post_page::render_blog_post, contact::render_contact, index::render_index,
|
blog_post_page::render_blog_post, broadcast_list::render_broadcast_post_list,
|
||||||
project_list::render_projects_list,
|
contact::render_contact, index::render_index, project_list::render_projects_list,
|
||||||
|
showcase::egg_fetcher::render_egg_fetcher,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use axum::{extract::MatchedPath, http::Request, routing::get, Router};
|
use axum::{extract::MatchedPath, http::Request, routing::get, Router};
|
||||||
@ -16,8 +17,12 @@ pub fn get_router() -> Router {
|
|||||||
.route("/blog", get(render_blog_post_list))
|
.route("/blog", get(render_blog_post_list))
|
||||||
.route("/blog/tags/:tag", get(render_blog_post_list))
|
.route("/blog/tags/:tag", get(render_blog_post_list))
|
||||||
.route("/blog/:post_id", get(render_blog_post))
|
.route("/blog/:post_id", get(render_blog_post))
|
||||||
|
.route("/broadcasts", get(render_broadcast_post_list))
|
||||||
|
.route("/broadcasts/tags/:tag", get(render_broadcast_post_list))
|
||||||
|
.route("/broadcasts/:post_id", get(render_blog_post))
|
||||||
.route("/contact", get(render_contact))
|
.route("/contact", get(render_contact))
|
||||||
.route("/showcase", get(render_projects_list))
|
.route("/showcase", get(render_projects_list))
|
||||||
|
.route("/showcase/:project_slug", get(render_egg_fetcher))
|
||||||
.route("/admin", get(render_admin))
|
.route("/admin", get(render_admin))
|
||||||
.route("/feed.xml", get(render_rss_feed))
|
.route("/feed.xml", get(render_rss_feed))
|
||||||
.layer(
|
.layer(
|
||||||
|
@ -2125,6 +2125,11 @@ article a:visited {
|
|||||||
.xl\:grid-cols-\[1fr_2fr\] {
|
.xl\:grid-cols-\[1fr_2fr\] {
|
||||||
grid-template-columns: 1fr 2fr;
|
grid-template-columns: 1fr 2fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.xl\:gap-x-32 {
|
||||||
|
-moz-column-gap: 8rem;
|
||||||
|
column-gap: 8rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media print {
|
@media print {
|
||||||
|
@ -26,8 +26,6 @@
|
|||||||
<!-- Tailwind output file -->
|
<!-- Tailwind output file -->
|
||||||
<link rel="stylesheet" href="/styles/output.css" />
|
<link rel="stylesheet" href="/styles/output.css" />
|
||||||
|
|
||||||
<link rel="manifest" href="/manifest.json" />
|
|
||||||
|
|
||||||
<link rel="icon" type="image/svg+xml" href="/images/m-logo.svg" />
|
<link rel="icon" type="image/svg+xml" href="/images/m-logo.svg" />
|
||||||
<link rel="icon" type="image/png" href="/images/m-logo-192.png" />
|
<link rel="icon" type="image/png" href="/images/m-logo-192.png" />
|
||||||
</head>
|
</head>
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
{% block og_meta %}
|
{% block og_meta %}
|
||||||
<meta property="og:title" content="{{title}}" />
|
<meta property="og:title" content="{{title}}" />
|
||||||
<meta property="og:type" content="article" />
|
<meta property="og:type" content="article" />
|
||||||
<meta property="og:url" content="https://michalvanko.dev/blog/{{slug}}" />
|
<meta property="og:url" content="https://michalvanko.dev/{{segment}}/{{slug}}" />
|
||||||
{% match thumbnail %}
|
{% match thumbnail %}
|
||||||
{% when Some with (img) %}
|
{% when Some with (img) %}
|
||||||
{% let src = crate::picture_generator::image_src_generator::generate_image_with_src(img, 1200, 630, "_og", true).unwrap_or("thumbnail not found".to_string())|safe %}
|
{% let src = crate::picture_generator::image_src_generator::generate_image_with_src(img, 1200, 630, "_og", true).unwrap_or("thumbnail not found".to_string())|safe %}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block og_meta %}
|
{% block og_meta %}
|
||||||
<meta property="og:title" content="{{title}} @michalvankodev" />
|
<meta property="og:title" content="{{og_title}} @michalvankodev" />
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
<meta property="og:url" content="https://michalvanko.dev{{current_url}}" />
|
<meta property="og:url" content="https://michalvanko.dev{{current_url}}" />
|
||||||
<meta property="og:image" content="https://michalvanko.dev/images/m-logo.svg" />
|
<meta property="og:image" content="https://michalvanko.dev/images/m-logo.svg" />
|
||||||
@ -10,24 +10,20 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<section id="blog-container" class="lg:grid lg:grid-cols-[2fr_1fr] lg:grid-rows-[min-content_1fr] lg:gap-x-32 max-w-maxindex mx-auto">
|
<section id="blog-container" class="lg:grid lg:grid-cols-[2fr_1fr] lg:grid-rows-[min-content_1fr] xl:gap-x-32 max-w-maxindex mx-auto">
|
||||||
<section id="blog-list" class="lg:row-span-2">
|
<section id="blog-list" class="lg:row-span-2">
|
||||||
{% if posts.len() == 0 %}
|
{% if posts.len() == 0 %}
|
||||||
<p class="no-posts">You've found void in the space.</p>
|
<p class="no-posts">You've found void in the space.</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
<h1 class="m-5 text-4xl text-blue-950 font-extrabold md:text-6xl">
|
<h1 class="m-5 text-4xl text-blue-950 font-extrabold md:text-6xl">
|
||||||
{% if let Some(t) = tag %}
|
{{title}}
|
||||||
#{{t}}
|
|
||||||
{% else %}
|
|
||||||
Blog posts
|
|
||||||
{% endif %}
|
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<section id="blog-tags">
|
<section id="blog-tags">
|
||||||
<ul class="mx-5">
|
<ul class="mx-5">
|
||||||
{% for tag in blog_tags %}
|
{% for tag in tags %}
|
||||||
<li class="inline-block mx-0.5 p-0.5 md:text-xl">
|
<li class="inline-block mx-0.5 p-0.5 md:text-xl">
|
||||||
<a href="/blog/tags/{{tag}}" class="text-pink-950">#{{tag|capitalize}}</a>
|
<a href="/{{segment}}/tags/{{tag}}" class="text-pink-950">#{{tag|capitalize}}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
</aside>
|
</aside>
|
||||||
<header>
|
<header>
|
||||||
<h3 class="text-lg font-bold mb-1 md:text-3xl">
|
<h3 class="text-lg font-bold mb-1 md:text-3xl">
|
||||||
<a rel="prefetch" href="/blog/{{post.slug}}" class="text-blue-950 visited:text-purple-700 no-underline">{{post.metadata.title}}</a>
|
<a rel="prefetch" href="/{{segment}}/{{post.slug}}" class="text-blue-950 visited:text-purple-700 no-underline">{{post.metadata.title}}</a>
|
||||||
</h3>
|
</h3>
|
||||||
</header>
|
</header>
|
||||||
<section class="text-base leading-5 text-slate-800 md:text-xl text-justify">{{post.body|description_filter|safe}}</section>
|
<section class="text-base leading-5 text-slate-800 md:text-xl text-justify">{{post.body|description_filter|safe}}</section>
|
||||||
@ -19,7 +19,7 @@
|
|||||||
<ul class="inline-block">
|
<ul class="inline-block">
|
||||||
{% for tag in post.metadata.tags %}
|
{% for tag in post.metadata.tags %}
|
||||||
<li class="inline-block">
|
<li class="inline-block">
|
||||||
<a href="/blog/tags/{{tag}}" class="text-pink-950 no-underline">#{{tag|capitalize}}</a>
|
<a href="/{{segment}}/tags/{{tag}}" class="text-pink-950 no-underline">#{{tag|capitalize}}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
16
templates/egg_fetcher_page.html
Normal file
16
templates/egg_fetcher_page.html
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Egg fetcher showcase{% endblock %}
|
||||||
|
|
||||||
|
{% block og_meta %}
|
||||||
|
<meta property="og:title" content="Egg fetcher showcase" />
|
||||||
|
<meta property="og:type" content="article" />
|
||||||
|
<meta property="og:url" content="https://michalvanko.dev/showcase/egg-fetcher" />
|
||||||
|
<meta property="og:image" content="https://michalvanko.dev/images/m-logo.svg" />
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<article class="article-body">
|
||||||
|
{% include "showcase/egg-fetcher.html" %}
|
||||||
|
</article>
|
||||||
|
{% endblock %}
|
@ -41,12 +41,13 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="blog" class="lg:col-span-2 lg:row-start-2 xl:col-auto xl:row-start-auto xl:row-span-2">
|
<section id="blog" class="lg:col-span-2 lg:row-start-2 xl:col-auto xl:row-start-auto xl:row-span-2">
|
||||||
|
{% let segment = "blog".to_string() %}
|
||||||
<h2 class="text-blue-950 font-bold text-2xl md:text-4xl m-5"><a href="/blog" class="text-blue-950 no-underline">Blog</a></h2>
|
<h2 class="text-blue-950 font-bold text-2xl md:text-4xl m-5"><a href="/blog" class="text-blue-950 no-underline">Blog</a></h2>
|
||||||
<section id="blog-tags">
|
<section id="blog-tags">
|
||||||
<ul class="mx-5">
|
<ul class="mx-5">
|
||||||
{% for tag in blog_tags %}
|
{% for tag in blog_tags %}
|
||||||
<li class="inline-block mx-0.5 p-0.5 md:text-xl">
|
<li class="inline-block mx-0.5 p-0.5 md:text-xl">
|
||||||
<a href="/blog/tags/{{tag}}" class="text-pink-950">#{{tag|capitalize}}</a>
|
<a href="/{{segment}}/tags/{{tag}}" class="text-pink-950">#{{tag|capitalize}}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<ul class="inline">
|
<ul class="inline">
|
||||||
{% for tag in tags %}
|
{% for tag in tags %}
|
||||||
<li class="inline italic text-blue-700 md:text-lg">
|
<li class="inline italic text-blue-700 md:text-lg">
|
||||||
<a href="/blog/tags/{{tag}}">#{{tag}}</a>
|
<a href="/{{segment}}/tags/{{tag}}">#{{tag}}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
15
templates/showcase/egg-fetcher.html
Normal file
15
templates/showcase/egg-fetcher.html
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<h1>Egg-fetcher</h1>
|
||||||
|
|
||||||
|
<p>As mentioned in <a href="/blog/2022-06-26-our-attempt-at-rusty-game-jam-weekly-25-2022">Weekly #25-2022</a>, I've attended the <a href="https://itch.io/jam/rusty-jam-2">Rusty game jam #2</a> where we had a week to <strong>create a game with Rust</strong>.</p>
|
||||||
|
<p>I've teamed up with <a href="https://github.com/silen-z/">@silen-z</a>. We haven't been able to finish the game. We didn't even make the mechanics we were thinking of. But I'd like to show <strong>the incomplete version of Egg-fetcher</strong> anyway. As we built it with Rust and <a href="https://bevyengine.org/">bevy engine</a> we were able to <a href="https://github.com/septum/rusty_jam_bevy_template">reuse a template</a> that had a configured WASM build. Therefore is very easy to just present the game in the browser.</p>
|
||||||
|
<iframe title="Egg fetcher game" src="/egg-fetcher/index.html" width="800" height="600"></iframe>
|
||||||
|
<p><strong>The only functional controls are arrows</strong>. We have built a collision system where the chickens should move out of the way of the player and his pet. The player is not able to move through the fences and so on. We wanted to <strong>create a puzzle</strong> where you would have to play fetch with your pet dog to control the chickens and simultaneously control player movement.</p>
|
||||||
|
<p>This was only my 3rd attempt at a Rust codebase and therefore I got pretty much always stuck at some problem with borrow-checker or lifetimes.
|
||||||
|
I learned many things and I'd like to continue with Rust and use it more in my side-projects.</p>
|
||||||
|
<style>
|
||||||
|
iframe {
|
||||||
|
height: 720px;
|
||||||
|
width: 1280px !important;
|
||||||
|
max-width: 1280px !important;
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
Reference in New Issue
Block a user