feat: add tasks rendering
This commit is contained in:
parent
e79797fe5f
commit
e1d575bce9
62
Cargo.lock
generated
62
Cargo.lock
generated
@ -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"
|
||||
|
@ -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"
|
||||
|
12
src/base.rs
12
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" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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..." }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,2 +1,3 @@
|
||||
mod rendering;
|
||||
pub mod tasks;
|
||||
pub mod tasks_controller;
|
||||
|
30
src/tasks/rendering.rs
Normal file
30
src/tasks/rendering.rs
Normal 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",
|
||||
})) {}
|
||||
}
|
||||
}
|
||||
}
|
@ -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<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) {
|
||||
0 => TaskLabel::Bug,
|
||||
1 => TaskLabel::Feature,
|
||||
@ -76,9 +79,9 @@ impl Distribution<TaskPriority> for Standard {
|
||||
impl Distribution<Task> for Standard {
|
||||
fn sample<R: Rng + ?Sized>(&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<Task> for Standard {
|
||||
|
||||
static TASKS: OnceLock<Mutex<Vec<Task>>> = OnceLock::new();
|
||||
|
||||
pub fn all() -> Vec<Task> {
|
||||
TASKS
|
||||
.get_or_init(|| Mutex::new((0..100).map(|_| rand::random::<Task>()).collect()))
|
||||
.lock()
|
||||
.unwrap()
|
||||
.clone()
|
||||
pub async fn all() -> Option<Vec<Task>> {
|
||||
Some(
|
||||
TASKS
|
||||
.get_or_init(|| Mutex::new((0..100).map(|_| rand::random::<Task>()).collect()))
|
||||
.lock()
|
||||
.unwrap()
|
||||
.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn by_id(id: i32) -> Option<Task> {
|
||||
pub async fn by_id(id: i32) -> Option<Task> {
|
||||
TASKS
|
||||
.get_or_init(|| Mutex::new((0..100).map(|_| rand::random::<Task>()).collect()))
|
||||
.lock()
|
||||
@ -104,50 +109,3 @@ pub fn by_id(id: i32) -> Option<Task> {
|
||||
.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,
|
||||
}
|
||||
|
||||
*/
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
5
src/traits/html_renderable.rs
Normal file
5
src/traits/html_renderable.rs
Normal file
@ -0,0 +1,5 @@
|
||||
use maud::Markup;
|
||||
|
||||
pub trait HtmlRenderable {
|
||||
fn to_html(&self) -> Markup;
|
||||
}
|
1
src/traits/mod.rs
Normal file
1
src/traits/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod html_renderable;
|
Reference in New Issue
Block a user