diff --git a/Cargo.lock b/Cargo.lock index 6f94500..12a6897 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -282,7 +282,7 @@ dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim", + "strsim 0.11.1", ] [[package]] @@ -399,6 +399,41 @@ dependencies = [ "syn 2.0.60", ] +[[package]] +name = "darling" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn 2.0.60", +] + +[[package]] +name = "darling_macro" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.60", +] + [[package]] name = "dashmap" version = "5.5.3" @@ -448,6 +483,18 @@ dependencies = [ "dtoa", ] +[[package]] +name = "dummy" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e57e12b69e57fad516e01e2b3960f122696fdb13420e1a88ed8e210316f2876" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.60", +] + [[package]] name = "either" version = "1.11.0" @@ -467,6 +514,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c25829bde82205da46e1823b2259db6273379f626fc211f126f65654a2669be" dependencies = [ "deunicode", + "dummy", "rand", ] @@ -703,6 +751,12 @@ dependencies = [ "tokio", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "indexmap" version = "2.2.6" @@ -1465,6 +1519,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "strsim" version = "0.11.1" diff --git a/Cargo.toml b/Cargo.toml index 8d0dbcb..7a7da15 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ build = "build.rs" [dependencies] axum = "0.7.5" -fake = "2.9.2" +fake = { version = "2.9.2", features = ["derive"] } maud = { version = "0.26.0", features = ["axum"] } rand = "0.8.5" strum = "0.26.2" diff --git a/src/base.rs b/src/base.rs index 904ef77..fb3eb66 100644 --- a/src/base.rs +++ b/src/base.rs @@ -10,18 +10,6 @@ pub fn header() -> Markup { link rel="stylesheet" href="/public/styles/global.css"; link rel="icon" href="/public/favicon.ico"; } - section class="hero is-primary is-small"{ - div class="hero-head" { - nav class="navbar" role="navigation" aria-label="main navigation" { - div class="container" { - div class="navbar-brand" { - img src="/public/images/logo.svg" alt="logo"; - h1 class="title" { "Clego" } - } - } - } - } - } } } diff --git a/src/main.rs b/src/main.rs index 70f13bf..fd1d50b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +use crate::tasks::tasks_controller; use axum::{routing::get, Router}; use maud::{html, Markup}; use tower_http::services::ServeDir; @@ -6,6 +7,7 @@ use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; mod base; mod tasks; +mod traits; #[tokio::main] async fn main() { @@ -21,6 +23,7 @@ async fn main() { let router = Router::new() .route("/", get(hello)) + .nest("/tasks", tasks_controller::router()) .nest_service("/public", ServeDir::new("public")); let port = 3000_u16; let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", port)) @@ -34,8 +37,8 @@ async fn main() { async fn hello() -> Markup { html! { (base::header()) - main class="content" { - h1 { "Howdy!" } - } + main class="content" #content { + p hx-get="/tasks" hx-trigger="load" hx-swap="outerHTML" { "Loading..." } + } } } diff --git a/src/tasks/mod.rs b/src/tasks/mod.rs index 948beb8..39094ed 100644 --- a/src/tasks/mod.rs +++ b/src/tasks/mod.rs @@ -1,2 +1,3 @@ +mod rendering; pub mod tasks; pub mod tasks_controller; diff --git a/src/tasks/rendering.rs b/src/tasks/rendering.rs new file mode 100644 index 0000000..f282caf --- /dev/null +++ b/src/tasks/rendering.rs @@ -0,0 +1,30 @@ +use crate::traits::html_renderable::HtmlRenderable; +use maud::{html, Markup}; + +use super::tasks::{TaskPriority, TaskStatus}; + +impl HtmlRenderable for TaskPriority { + fn to_html(&self) -> Markup { + html! { + i class=(format!("priority-icon {}", match self { + TaskPriority::Low => "icon-low", + TaskPriority::Medium => "icon-medium", + TaskPriority::High => "icon-high", + })) {} + } + } +} + +impl HtmlRenderable for TaskStatus { + fn to_html(&self) -> Markup { + html! { + i class=(format!("status-icon {}", match self { + TaskStatus::Backlog => "icon-backlog", + TaskStatus::Todo => "icon-todo", + TaskStatus::InProgress => "icon-in-progress", + TaskStatus::Done => "icon-done", + TaskStatus::Canceled => "icon-canceled", + })) {} + } + } +} diff --git a/src/tasks/tasks.rs b/src/tasks/tasks.rs index 34e7b67..28a8eaf 100644 --- a/src/tasks/tasks.rs +++ b/src/tasks/tasks.rs @@ -1,5 +1,8 @@ -use fake::{Fake, Faker}; -use rand::{distributions::{Distribution, Standard}, Rng}; +use fake::{faker::lorem::fr_fr::Sentence, Fake}; +use rand::{ + distributions::{Distribution, Standard}, + Rng, +}; use std::sync::{Mutex, OnceLock}; use strum_macros::{self, EnumIter, EnumString}; @@ -9,13 +12,13 @@ use strum::IntoEnumIterator; #[derive(Clone, Debug)] pub struct Task { pub id: i32, - pub label: TaskLabel, pub title: String, + pub label: TaskLabel, pub status: TaskStatus, pub priority: TaskPriority, } -#[derive(Clone, Debug, EnumString, EnumIter)] +#[derive(Clone, Debug, EnumString, EnumIter, strum_macros::Display)] #[strum(serialize_all = "snake_case")] pub enum TaskLabel { Bug, @@ -23,7 +26,7 @@ pub enum TaskLabel { Documentation, } -#[derive(Clone, Debug, EnumString, EnumIter)] +#[derive(Clone, Debug, EnumString, EnumIter, strum_macros::Display)] #[strum(serialize_all = "snake_case")] pub enum TaskStatus { Backlog, @@ -33,7 +36,7 @@ pub enum TaskStatus { Canceled, } -#[derive(Clone, Debug, EnumString, EnumIter)] +#[derive(Clone, Debug, EnumString, EnumIter, strum_macros::Display)] #[strum(serialize_all = "snake_case")] pub enum TaskPriority { Low, @@ -42,7 +45,7 @@ pub enum TaskPriority { } impl Distribution for Standard { - fn sample(&self, rng: &mut R) -> TaskLabel { + fn sample(&self, rng: &mut R) -> TaskLabel { match rng.gen_range(0..3) { 0 => TaskLabel::Bug, 1 => TaskLabel::Feature, @@ -76,9 +79,9 @@ impl Distribution for Standard { impl Distribution for Standard { fn sample(&self, rng: &mut R) -> Task { Task { - id: Faker.fake(), + id: (1000..2000).fake(), + title: Sentence(10..20).fake(), label: rng.gen(), - title: Faker.fake(), status: rng.gen(), priority: rng.gen(), } @@ -87,15 +90,17 @@ impl Distribution for Standard { static TASKS: OnceLock>> = OnceLock::new(); -pub fn all() -> Vec { - TASKS - .get_or_init(|| Mutex::new((0..100).map(|_| rand::random::()).collect())) - .lock() - .unwrap() - .clone() +pub async fn all() -> Option> { + Some( + TASKS + .get_or_init(|| Mutex::new((0..100).map(|_| rand::random::()).collect())) + .lock() + .unwrap() + .clone(), + ) } -pub fn by_id(id: i32) -> Option { +pub async fn by_id(id: i32) -> Option { TASKS .get_or_init(|| Mutex::new((0..100).map(|_| rand::random::()).collect())) .lock() @@ -104,50 +109,3 @@ pub fn by_id(id: i32) -> Option { .find(|task| task.id == id) .cloned() } - -/* -* -use fake::{Fake, Faker}; -use rand::Rng; -use std::sync::Mutex; -use strum_macros::{self, EnumString}; -use tokio::sync::Mutex; - -#[allow(unused_imports)] // bring trait into scope -use strum::EnumProperty; - -#[derive(Clone, Debug)] -pub struct Task { - pub id: i32, - pub label: TaskLabel, - pub title: String, - pub status: TaskStatus, - pub priority: TaskPriority, -} - -#[derive(Clone, Debug, EnumString)] -#[strum(serialize_all = "snake_case")] -enum TaskLabel { - Bug, - Feature, - Documentation, -} - -#[derive(Clone, Debug, EnumString)] -#[strum(serialize_all = "snake_case")] -enum TaskStatus { - Backlog, - Todo, - InProgress, - Done, - Canceled, -} -#[derive(Clone, Debug, EnumString, strum_macros::EnumProperty)] -#[strum(serialize_all = "snake_case")] -enum TaskPriority { - Low, - Medium, - High, -} - -*/ diff --git a/src/tasks/tasks_controller.rs b/src/tasks/tasks_controller.rs index 8b13789..2fee9b7 100644 --- a/src/tasks/tasks_controller.rs +++ b/src/tasks/tasks_controller.rs @@ -1 +1,69 @@ +use crate::base; +use axum::Router; +use axum::{extract::Path, routing::get}; +use maud::{html, Markup}; +use super::tasks::{self, Task}; + +pub fn router() -> Router { + Router::new() + .route("/", get(index)) + .route("/:id", get(task)) +} + +pub async fn index() -> Markup { + let tasks = tasks::all().await; + + match tasks { + Some(tasks) => index_tmpl(tasks), + None => base::error_tmpl(), + } +} + +fn index_tmpl(tasks: Vec) -> Markup { + html! { + div class="table-container" { + table class="table is-hoverable is-fullwidth" { + thead { + tr { + th { "Task" } + th { "Title" } + th { "Status" } + th { "Priority" } + th {} + } + } + tbody { + @for task in &tasks{ + tr { + th { (task.id) } + td { span class="tag is-black" { (task.label) } (task.title) } + td { (task.status) } + td { (task.priority) } + td { button hx-get={ (format!("/tasks/{}", task.id)) } hx-push-url="true" hx-target="closest main" { "Read more" } } + } + } + } + } + } + } +} + +async fn task(Path(id): Path) -> Markup { + let task = tasks::by_id(id).await; + + match task { + Some(task) => task_tmpl(task), + None => base::error_tmpl(), + } +} + +fn task_tmpl(task: Task) -> Markup { + html! { + h2 { (task.title) } + button hx-get="/tasks" hx-target="closest main" { + "Go back" + } + hr; + } +} diff --git a/src/traits/html_renderable.rs b/src/traits/html_renderable.rs new file mode 100644 index 0000000..f44e9fb --- /dev/null +++ b/src/traits/html_renderable.rs @@ -0,0 +1,5 @@ +use maud::Markup; + +pub trait HtmlRenderable { + fn to_html(&self) -> Markup; +} diff --git a/src/traits/mod.rs b/src/traits/mod.rs new file mode 100644 index 0000000..72fc616 --- /dev/null +++ b/src/traits/mod.rs @@ -0,0 +1 @@ +pub mod html_renderable;