broadcasts and 404 errors
Some checks failed
test / cargo test (push) Failing after 59s

This commit is contained in:
Michal Vanko 2024-10-02 15:32:40 +02:00
parent 4f09373df3
commit 2979e21285
23 changed files with 236 additions and 60 deletions

View File

@ -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

View File

@ -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
View File

@ -0,0 +1,2 @@
User-agent: *
Disallow: /admin

View File

@ -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,
}
} }

View File

@ -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))
} }

View File

@ -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
//

View File

@ -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(),
}) })

View File

@ -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(),
}),
}) })
} }

View 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(),
})
}

View File

@ -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()
)?; )?;

View File

@ -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;

View 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(),
})
}

View File

@ -0,0 +1 @@
pub mod egg_fetcher;

View File

@ -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(

View File

@ -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 {

View File

@ -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>

View File

@ -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 %}

View File

@ -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>

View File

@ -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>

View 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 %}

View File

@ -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>

View File

@ -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>

View 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>