feat: add tasks rendering

This commit is contained in:
theo 2024-05-01 22:40:56 +02:00
parent e79797fe5f
commit e1d575bce9
10 changed files with 194 additions and 80 deletions

62
Cargo.lock generated
View File

@ -282,7 +282,7 @@ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
"clap_lex", "clap_lex",
"strsim", "strsim 0.11.1",
] ]
[[package]] [[package]]
@ -399,6 +399,41 @@ dependencies = [
"syn 2.0.60", "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]] [[package]]
name = "dashmap" name = "dashmap"
version = "5.5.3" version = "5.5.3"
@ -448,6 +483,18 @@ dependencies = [
"dtoa", "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]] [[package]]
name = "either" name = "either"
version = "1.11.0" version = "1.11.0"
@ -467,6 +514,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c25829bde82205da46e1823b2259db6273379f626fc211f126f65654a2669be" checksum = "1c25829bde82205da46e1823b2259db6273379f626fc211f126f65654a2669be"
dependencies = [ dependencies = [
"deunicode", "deunicode",
"dummy",
"rand", "rand",
] ]
@ -703,6 +751,12 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.2.6" version = "2.2.6"
@ -1465,6 +1519,12 @@ dependencies = [
"windows-sys 0.52.0", "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]] [[package]]
name = "strsim" name = "strsim"
version = "0.11.1" version = "0.11.1"

View File

@ -8,7 +8,7 @@ build = "build.rs"
[dependencies] [dependencies]
axum = "0.7.5" axum = "0.7.5"
fake = "2.9.2" fake = { version = "2.9.2", features = ["derive"] }
maud = { version = "0.26.0", features = ["axum"] } maud = { version = "0.26.0", features = ["axum"] }
rand = "0.8.5" rand = "0.8.5"
strum = "0.26.2" strum = "0.26.2"

View File

@ -10,18 +10,6 @@ pub fn header() -> Markup {
link rel="stylesheet" href="/public/styles/global.css"; link rel="stylesheet" href="/public/styles/global.css";
link rel="icon" href="/public/favicon.ico"; 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" }
}
}
}
}
}
} }
} }

View File

@ -1,3 +1,4 @@
use crate::tasks::tasks_controller;
use axum::{routing::get, Router}; use axum::{routing::get, Router};
use maud::{html, Markup}; use maud::{html, Markup};
use tower_http::services::ServeDir; use tower_http::services::ServeDir;
@ -6,6 +7,7 @@ use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
mod base; mod base;
mod tasks; mod tasks;
mod traits;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
@ -21,6 +23,7 @@ async fn main() {
let router = Router::new() let router = Router::new()
.route("/", get(hello)) .route("/", get(hello))
.nest("/tasks", tasks_controller::router())
.nest_service("/public", ServeDir::new("public")); .nest_service("/public", ServeDir::new("public"));
let port = 3000_u16; let port = 3000_u16;
let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", port)) let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", port))
@ -34,8 +37,8 @@ async fn main() {
async fn hello() -> Markup { async fn hello() -> Markup {
html! { html! {
(base::header()) (base::header())
main class="content" { main class="content" #content {
h1 { "Howdy!" } p hx-get="/tasks" hx-trigger="load" hx-swap="outerHTML" { "Loading..." }
} }
} }
} }

View File

@ -1,2 +1,3 @@
mod rendering;
pub mod tasks; pub mod tasks;
pub mod tasks_controller; pub mod tasks_controller;

30
src/tasks/rendering.rs Normal file
View File

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

View File

@ -1,5 +1,8 @@
use fake::{Fake, Faker}; use fake::{faker::lorem::fr_fr::Sentence, Fake};
use rand::{distributions::{Distribution, Standard}, Rng}; use rand::{
distributions::{Distribution, Standard},
Rng,
};
use std::sync::{Mutex, OnceLock}; use std::sync::{Mutex, OnceLock};
use strum_macros::{self, EnumIter, EnumString}; use strum_macros::{self, EnumIter, EnumString};
@ -9,13 +12,13 @@ use strum::IntoEnumIterator;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Task { pub struct Task {
pub id: i32, pub id: i32,
pub label: TaskLabel,
pub title: String, pub title: String,
pub label: TaskLabel,
pub status: TaskStatus, pub status: TaskStatus,
pub priority: TaskPriority, pub priority: TaskPriority,
} }
#[derive(Clone, Debug, EnumString, EnumIter)] #[derive(Clone, Debug, EnumString, EnumIter, strum_macros::Display)]
#[strum(serialize_all = "snake_case")] #[strum(serialize_all = "snake_case")]
pub enum TaskLabel { pub enum TaskLabel {
Bug, Bug,
@ -23,7 +26,7 @@ pub enum TaskLabel {
Documentation, Documentation,
} }
#[derive(Clone, Debug, EnumString, EnumIter)] #[derive(Clone, Debug, EnumString, EnumIter, strum_macros::Display)]
#[strum(serialize_all = "snake_case")] #[strum(serialize_all = "snake_case")]
pub enum TaskStatus { pub enum TaskStatus {
Backlog, Backlog,
@ -33,7 +36,7 @@ pub enum TaskStatus {
Canceled, Canceled,
} }
#[derive(Clone, Debug, EnumString, EnumIter)] #[derive(Clone, Debug, EnumString, EnumIter, strum_macros::Display)]
#[strum(serialize_all = "snake_case")] #[strum(serialize_all = "snake_case")]
pub enum TaskPriority { pub enum TaskPriority {
Low, Low,
@ -42,7 +45,7 @@ pub enum TaskPriority {
} }
impl Distribution<TaskLabel> for Standard { impl Distribution<TaskLabel> for Standard {
fn sample<R: Rng+ ?Sized>(&self, rng: &mut R) -> TaskLabel { fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> TaskLabel {
match rng.gen_range(0..3) { match rng.gen_range(0..3) {
0 => TaskLabel::Bug, 0 => TaskLabel::Bug,
1 => TaskLabel::Feature, 1 => TaskLabel::Feature,
@ -76,9 +79,9 @@ impl Distribution<TaskPriority> for Standard {
impl Distribution<Task> for Standard { impl Distribution<Task> for Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Task { fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Task {
Task { Task {
id: Faker.fake(), id: (1000..2000).fake(),
title: Sentence(10..20).fake(),
label: rng.gen(), label: rng.gen(),
title: Faker.fake(),
status: rng.gen(), status: rng.gen(),
priority: rng.gen(), priority: rng.gen(),
} }
@ -87,15 +90,17 @@ impl Distribution<Task> for Standard {
static TASKS: OnceLock<Mutex<Vec<Task>>> = OnceLock::new(); static TASKS: OnceLock<Mutex<Vec<Task>>> = OnceLock::new();
pub fn all() -> Vec<Task> { pub async fn all() -> Option<Vec<Task>> {
TASKS Some(
.get_or_init(|| Mutex::new((0..100).map(|_| rand::random::<Task>()).collect())) TASKS
.lock() .get_or_init(|| Mutex::new((0..100).map(|_| rand::random::<Task>()).collect()))
.unwrap() .lock()
.clone() .unwrap()
.clone(),
)
} }
pub fn by_id(id: i32) -> Option<Task> { pub async fn by_id(id: i32) -> Option<Task> {
TASKS TASKS
.get_or_init(|| Mutex::new((0..100).map(|_| rand::random::<Task>()).collect())) .get_or_init(|| Mutex::new((0..100).map(|_| rand::random::<Task>()).collect()))
.lock() .lock()
@ -104,50 +109,3 @@ pub fn by_id(id: i32) -> Option<Task> {
.find(|task| task.id == id) .find(|task| task.id == id)
.cloned() .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,
}
*/

View File

@ -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<Task>) -> 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<i32>) -> 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;
}
}

View File

@ -0,0 +1,5 @@
use maud::Markup;
pub trait HtmlRenderable {
fn to_html(&self) -> Markup;
}

1
src/traits/mod.rs Normal file
View File

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