diff --git a/Cargo.lock b/Cargo.lock index 2b22407..d4b4ccf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -152,7 +152,7 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "based" version = "0.1.0" -source = "git+https://git.hydrar.de/jmarya/based?branch=ui#1e0ab7e0dcd42c304146376176d15ff34784bb34" +source = "git+https://git.hydrar.de/jmarya/based?branch=ui#cdaabccd9eb701c5f0050def9706fc167137a481" dependencies = [ "bcrypt", "chrono", @@ -257,9 +257,9 @@ checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" [[package]] name = "cc" -version = "1.2.12" +version = "1.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "755717a7de9ec452bf7f3f1a3099085deabd7f2962b861dae91ecd7a365903d2" +checksum = "c7777341816418c02e033934a09f20dc0ccaf65a5201ef8a450ae0105a573fda" dependencies = [ "shlex", ] diff --git a/config.toml b/config.toml index c15370b..20ba85c 100644 --- a/config.toml +++ b/config.toml @@ -27,3 +27,7 @@ contact.email = "mail@example.com" [project.sub.name] name = "Sub Project" description = "Sub Project" + +[project.sub.name.sub.subname] +name = "Sub Sub Project" +description = "Sub Sub Project" diff --git a/src/config.rs b/src/config.rs index 6fa3852..4695df2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -29,3 +29,15 @@ impl Config { toml::from_str(&content).unwrap() } } + +pub fn get_prj(c: &ProjectConfig, path: &str) -> Option { + let paths: Vec<_> = path.split("/").filter(|x| !x.is_empty()).collect(); + let name = paths.first()?.to_string(); + + if paths.len() == 1 { + return c.get(&name).cloned(); + } else { + let sub_prj = c.get(&name)?.sub.as_ref()?; + get_prj(sub_prj, &paths[1..].join("/")) + } +} diff --git a/src/main.rs b/src/main.rs index 598e39f..e5b711c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,12 +3,12 @@ use std::path::Path; use based::asset::AssetRoutes; use based::page; use based::request::{RequestContext, StringResponse}; -use based::ui::components::{Shell, Tabs}; +use based::ui::components::{Breadcrumb, Shell, Tabs}; use based::ui::prelude::*; use based::ui::primitives::flex::Row; use based::ui::primitives::Optional; -use config::{Project, ProjectConfig}; -use maud::Render; +use config::{get_prj, Project, ProjectConfig}; +use maud::{PreEscaped, Render}; use rocket::fs::NamedFile; use rocket::request::FromSegments; use rocket::{get, launch, routes, State}; @@ -61,11 +61,10 @@ pub async fn project_page( ) -> Option { let mut prj = c.get(path.segments.first()?); for p in &path.segments[1..] { - println!(" --> {p}"); prj = prj.as_ref()?.sub.as_ref()?.get(p); } - Some(render_project(shell, ctx, prj?, path.to_str()).await) + Some(render_project(shell, ctx, prj?, c, path.to_str()).await) } #[get("/static/")] @@ -85,100 +84,130 @@ pub async fn main_page( let first = keys.first().unwrap(); let prj = c.get(*first).unwrap(); - render_project(shell, ctx, prj, format!("{first}/")).await + render_project(shell, ctx, prj, c, format!("{first}/")).await } else { // TODO : root project overview site::gen_site(c, ctx).await } } +pub fn info_tab(prj: &Project, root: &str) -> PreEscaped { + Flex( + Div() + .vanish() + .push(Rounded( + Background(Padding(Code(&prj.description)).all(ScreenValue::_4)).color(Gray::_900), + )) + .push(Optional(prj.website.as_deref(), |website| { + Link(website, Text(&format!("Website: {website}"))) + })) + .push(Optional(prj.documentation.as_deref(), |docs| { + Link(docs, Text(&format!("Documentation: {docs}"))) + })) + .push(Optional(prj.since.as_deref(), |since| { + Text(&format!("Since: {since}")) + })) + .push(Optional(prj.contact.as_ref(), |contact| { + Div() + .vanish() + .push(Text("Contact").large()) + .push(Text(&format!( + "eMail: {}", + contact + .email + .as_ref() + .map(|x| x.as_str()) + .unwrap_or_default() + ))) + })) + .push(Optional(prj.sub.as_ref(), |sub| { + let mut prj_links = Vec::new(); + + for key in sub.keys() { + prj_links.push(key); + } + + Div() + .vanish() + .push(Text("Sub projects").large()) + .push_for_each(&prj_links, |link: &_| { + Width( + ScreenValue::fit, + Button(Link( + &format!("/prj/{root}{link}"), + Text(&prj.sub.as_ref().unwrap().get(*link).unwrap().name), + )), + ) + }) + })), + ) + .gap(ScreenValue::_2) + .direction(Direction::Column) + .render() +} + +fn up_to(keyword: &str, v: &[&str], separator: &str) -> String { + let index = v.iter().position(|s| *s == keyword).unwrap_or(v.len()); + v[..index + 1].join(separator) +} + +#[allow(non_snake_case)] +pub fn ProjectIcon(prj: &Project) -> PreEscaped { + Row(vec![ + Optional(prj.icon.as_ref(), |icon| Image(icon).alt("Project Icon")), + Text(&prj.name)._2xl().render(), + ]) + .gap(ScreenValue::_4) + .full_center() + .render() +} + pub async fn render_project( shell: &State, ctx: RequestContext, prj: &Project, - root: String, + c: &State, + mut root: String, ) -> StringResponse { let title = format!("{} - Umbrella ☂️", prj.name); + if !root.is_empty() && !root.ends_with('/') { + root.push_str("/"); + } + + let root_paths: Vec<_> = root.split("/").filter(|x| !x.is_empty()).collect(); + + let bc: Vec<_> = root_paths + .iter() + .map(|x| { + let prj_path = up_to(x, &root_paths, "/"); + + ( + ProjectIcon(&get_prj(c, &prj_path).unwrap()).0, + format!("/prj/{}", prj_path), + ) + }) + .collect(); + + println!("{bc:?}"); + page!( shell, ctx, title, Div() .vanish() - .push( - Margin( - Row(vec![ - Optional(prj.icon.as_ref(), |icon| Image(icon).alt("Project Icon")), - Text(&prj.name)._2xl().render() - ]) - .gap(ScreenValue::_4) - .full_center() - ) - .top(ScreenValue::_6) - .bottom(ScreenValue::_2) - ) + .push(Width( + ScreenValue::fit, + Margin(Breadcrumb(bc)) + .top(ScreenValue::_6) + .bottom(ScreenValue::_2) + .x(ScreenValue::auto) + )) .push( Padding( Tabs() - .add_tab( - "info", - Text("📜 Info").medium(), - Flex( - Div() - .vanish() - .push(Text(&prj.description)) - .push(Optional(prj.website.as_deref(), |website| Link( - website, - Text(&format!("Website: {website}")) - ))) - .push(Optional(prj.documentation.as_deref(), |docs| Link( - docs, - Text(&format!("Documentation: {docs}")) - ))) - .push(Optional(prj.since.as_deref(), |since| Text(&format!( - "Since: {since}" - )))) - .push(Optional(prj.contact.as_ref(), |contact| { - Div().vanish().push(Text("Contact").large()).push(Text( - &format!( - "eMail: {}", - contact - .email - .as_ref() - .map(|x| x.as_str()) - .unwrap_or_default() - ), - )) - })) - .push(Optional(prj.sub.as_ref(), |sub| { - let mut prj_links = Vec::new(); - - for key in sub.keys() { - prj_links.push(key); - } - - Div() - .vanish() - .push(Text("Sub projects").large()) - .push_for_each(&prj_links, |link: &_| { - Link( - &format!("/prj/{root}{link}"), - Text( - &prj.sub - .as_ref() - .unwrap() - .get(*link) - .unwrap() - .name, - ), - ) - }) - })) - ) - .gap(ScreenValue::_2) - .direction(Direction::Column) - ) + .add_tab("info", Text("📜 Info").medium(), info_tab(prj, &root)) .add_tab( "invest", Text("💵 Invest").medium(),