Compare commits
No commits in common. "create-task-list" and "main" have entirely different histories.
create-tas
...
main
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1 @@
|
|||||||
/target
|
/target
|
||||||
/public/styles
|
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"tabWidth": 4
|
|
||||||
}
|
|
2015
Cargo.lock
generated
2015
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
17
Cargo.toml
17
Cargo.toml
@ -2,24 +2,7 @@
|
|||||||
name = "clego-app"
|
name = "clego-app"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
build = "build.rs"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = "0.7.5"
|
|
||||||
fake = { version = "2.9.2", features = ["derive"] }
|
|
||||||
maud = { version = "0.26.0", features = ["axum"] }
|
|
||||||
rand = "0.8.5"
|
|
||||||
strum = "0.26.2"
|
|
||||||
strum_macros = "0.26.2"
|
|
||||||
tokio = { version = "1.37.0", features = ["full"] }
|
|
||||||
tower = "0.4.13"
|
|
||||||
tower-http = { version = "0.5.2", features = ["fs"] }
|
|
||||||
tracing = "0.1.40"
|
|
||||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
|
||||||
|
|
||||||
|
|
||||||
[build-dependencies]
|
|
||||||
lightningcss = "1.0.0-alpha.55"
|
|
||||||
grass = "0.13.2"
|
|
||||||
|
30
README.md
30
README.md
@ -1,31 +1 @@
|
|||||||
# clego-app
|
# clego-app
|
||||||
|
|
||||||
## Tech Stack
|
|
||||||
|
|
||||||
- Rust: 🦀
|
|
||||||
- Axum: for serving static assets and powering the backend API
|
|
||||||
- htmx: for reactivity on the UI
|
|
||||||
- Maud: HTML templating (debatable, we may want to switch to askama)
|
|
||||||
- lightningcss: bundle and minify css
|
|
||||||
|
|
||||||
## Font choice
|
|
||||||
|
|
||||||
Use default system fonts to enhance native feel as well as lower size, following [Modern Font Stacks](https://github.com/system-fonts/modern-font-stacks)
|
|
||||||
|
|
||||||
## Commit messages
|
|
||||||
|
|
||||||
See [link](https://www.conventionalcommits.org/fr/v1.0.0/)
|
|
||||||
|
|
||||||
## Development Environment
|
|
||||||
|
|
||||||
For automatic rebuilds on change install cargo-watch:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cargo install cargo-watch
|
|
||||||
```
|
|
||||||
|
|
||||||
Then to run it:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cargo watch -c run
|
|
||||||
```
|
|
||||||
|
32
build.rs
32
build.rs
@ -1,32 +0,0 @@
|
|||||||
use std::error::Error;
|
|
||||||
use std::fs::{self, File};
|
|
||||||
use std::io::Write;
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use lightningcss::bundler::{Bundler, FileProvider};
|
|
||||||
use lightningcss::printer::PrinterOptions;
|
|
||||||
use lightningcss::stylesheet::{MinifyOptions, ParserOptions};
|
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn Error>> {
|
|
||||||
let input_path = Path::new("styles/global.css");
|
|
||||||
let output_path = Path::new("public/styles/global.css");
|
|
||||||
|
|
||||||
if let Some(parent) = output_path.parent() {
|
|
||||||
fs::create_dir_all(parent)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let fs = FileProvider::new();
|
|
||||||
let mut bundler = Bundler::new(&fs, None, ParserOptions::default());
|
|
||||||
|
|
||||||
let mut stylesheet = bundler.bundle(input_path).unwrap();
|
|
||||||
let _ = stylesheet.minify(MinifyOptions::default());
|
|
||||||
let minified_css = stylesheet.to_css(PrinterOptions {
|
|
||||||
minify: true,
|
|
||||||
..PrinterOptions::default()
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut output_file = File::create(output_path)?;
|
|
||||||
output_file.write_all(minified_css.unwrap().code.as_bytes())?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
Binary file not shown.
Before Width: | Height: | Size: 4.2 KiB |
1
public/htmx.min.js
vendored
1
public/htmx.min.js
vendored
File diff suppressed because one or more lines are too long
20
src/base.rs
20
src/base.rs
@ -1,20 +0,0 @@
|
|||||||
use maud::{html, Markup, DOCTYPE};
|
|
||||||
|
|
||||||
pub fn header() -> Markup {
|
|
||||||
html! {
|
|
||||||
(DOCTYPE)
|
|
||||||
head {
|
|
||||||
meta charset="utf-8";
|
|
||||||
title class="title" { "Clego" }
|
|
||||||
script src="/public/htmx.min.js" {};
|
|
||||||
link rel="stylesheet" href="/public/styles/global.css";
|
|
||||||
link rel="icon" href="/public/favicon.ico";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn error_tmpl() -> Markup {
|
|
||||||
html! {
|
|
||||||
h1 { "Something went terribly wrong :(" }
|
|
||||||
}
|
|
||||||
}
|
|
45
src/main.rs
45
src/main.rs
@ -1,44 +1,3 @@
|
|||||||
use crate::tasks::tasks_controller;
|
fn main() {
|
||||||
use axum::{routing::get, Router};
|
println!("Hello, world!");
|
||||||
use maud::{html, Markup};
|
|
||||||
use tower_http::services::ServeDir;
|
|
||||||
use tracing::info;
|
|
||||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
|
||||||
|
|
||||||
mod base;
|
|
||||||
mod tasks;
|
|
||||||
mod traits;
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() {
|
|
||||||
tracing_subscriber::registry()
|
|
||||||
.with(
|
|
||||||
tracing_subscriber::EnvFilter::try_from_default_env()
|
|
||||||
.unwrap_or_else(|_| "clego_app=debug".into()),
|
|
||||||
)
|
|
||||||
.with(tracing_subscriber::fmt::layer())
|
|
||||||
.init();
|
|
||||||
|
|
||||||
info!("initializing router...");
|
|
||||||
|
|
||||||
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))
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
info!("router initialized, now listening on port {}", port);
|
|
||||||
axum::serve(listener, router).await.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn hello() -> Markup {
|
|
||||||
html! {
|
|
||||||
(base::header())
|
|
||||||
main class="content" #content {
|
|
||||||
p hx-get="/tasks" hx-trigger="load" hx-swap="outerHTML" { "Loading..." }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
mod rendering;
|
|
||||||
pub mod tasks;
|
|
||||||
pub mod tasks_controller;
|
|
@ -1,30 +0,0 @@
|
|||||||
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,111 +0,0 @@
|
|||||||
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};
|
|
||||||
|
|
||||||
#[allow(unused_imports)] // trait into scope
|
|
||||||
use strum::IntoEnumIterator;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct Task {
|
|
||||||
pub id: i32,
|
|
||||||
pub title: String,
|
|
||||||
pub label: TaskLabel,
|
|
||||||
pub status: TaskStatus,
|
|
||||||
pub priority: TaskPriority,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, EnumString, EnumIter, strum_macros::Display)]
|
|
||||||
#[strum(serialize_all = "snake_case")]
|
|
||||||
pub enum TaskLabel {
|
|
||||||
Bug,
|
|
||||||
Feature,
|
|
||||||
Documentation,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, EnumString, EnumIter, strum_macros::Display)]
|
|
||||||
#[strum(serialize_all = "snake_case")]
|
|
||||||
pub enum TaskStatus {
|
|
||||||
Backlog,
|
|
||||||
Todo,
|
|
||||||
InProgress,
|
|
||||||
Done,
|
|
||||||
Canceled,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, EnumString, EnumIter, strum_macros::Display)]
|
|
||||||
#[strum(serialize_all = "snake_case")]
|
|
||||||
pub enum TaskPriority {
|
|
||||||
Low,
|
|
||||||
Medium,
|
|
||||||
High,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Distribution<TaskLabel> for Standard {
|
|
||||||
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> TaskLabel {
|
|
||||||
match rng.gen_range(0..3) {
|
|
||||||
0 => TaskLabel::Bug,
|
|
||||||
1 => TaskLabel::Feature,
|
|
||||||
_ => TaskLabel::Documentation,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Distribution<TaskStatus> for Standard {
|
|
||||||
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> TaskStatus {
|
|
||||||
match rng.gen_range(0..5) {
|
|
||||||
0 => TaskStatus::Backlog,
|
|
||||||
1 => TaskStatus::Todo,
|
|
||||||
2 => TaskStatus::InProgress,
|
|
||||||
3 => TaskStatus::Done,
|
|
||||||
_ => TaskStatus::Canceled,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Distribution<TaskPriority> for Standard {
|
|
||||||
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> TaskPriority {
|
|
||||||
match rng.gen_range(0..3) {
|
|
||||||
0 => TaskPriority::Low,
|
|
||||||
1 => TaskPriority::Medium,
|
|
||||||
_ => TaskPriority::High,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Distribution<Task> for Standard {
|
|
||||||
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Task {
|
|
||||||
Task {
|
|
||||||
id: (1000..2000).fake(),
|
|
||||||
title: Sentence(10..20).fake(),
|
|
||||||
label: rng.gen(),
|
|
||||||
status: rng.gen(),
|
|
||||||
priority: rng.gen(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static TASKS: OnceLock<Mutex<Vec<Task>>> = OnceLock::new();
|
|
||||||
|
|
||||||
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 async fn by_id(id: i32) -> Option<Task> {
|
|
||||||
TASKS
|
|
||||||
.get_or_init(|| Mutex::new((0..100).map(|_| rand::random::<Task>()).collect()))
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.iter()
|
|
||||||
.find(|task| task.id == id)
|
|
||||||
.cloned()
|
|
||||||
}
|
|
@ -1,69 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
use maud::Markup;
|
|
||||||
|
|
||||||
pub trait HtmlRenderable {
|
|
||||||
fn to_html(&self) -> Markup;
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
pub mod html_renderable;
|
|
@ -1,33 +0,0 @@
|
|||||||
@import "missing.css";
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--main-font: "system-ui, sans-serif";
|
|
||||||
--secondary-font: var(--main-font);
|
|
||||||
--mono-font: "ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace";
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
@use "pico" with (
|
|
||||||
$theme-color: "sand",
|
|
||||||
$enable-semantic-container: true,
|
|
||||||
$enable-viewport: false,
|
|
||||||
$enable-classes: false
|
|
||||||
);
|
|
||||||
|
|
||||||
@use "bulma/sass/utilities" with (
|
|
||||||
$family-sans-serif: "system-ui, sans-serif",
|
|
||||||
$family-monospace:
|
|
||||||
"ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace"
|
|
||||||
);
|
|
||||||
|
|
||||||
@forward "bulma/sass/base";
|
|
||||||
@forward "bulma/sass/components/navbar";
|
|
||||||
@forward "bulma/sass/elements/title";
|
|
||||||
@forward "bulma/sass/elements/content";
|
|
||||||
@forward "bulma/sass/elements/table";
|
|
||||||
@forward "bulma/sass/elements/tag";
|
|
||||||
@forward "bulma/sass/layout/hero";
|
|
||||||
@forward "bulma/sass/layout/container";
|
|
||||||
|
|
||||||
@forward "bulma/sass/themes";
|
|
||||||
*/
|
|
4225
styles/missing.css
4225
styles/missing.css
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user