89 lines
2.6 KiB
Rust
89 lines
2.6 KiB
Rust
use crate::queries::{ListTodos, Todo};
|
||
use livevue_rs::{RenderContext, global, mangle};
|
||
use maud::{html, Markup};
|
||
|
||
pub async fn todo_list(cx: &mut RenderContext) -> anyhow::Result<Markup> {
|
||
// Fetch all todos (registers CacheKey::Table { table: "todos" })
|
||
let todos = cx.run(ListTodos).await?;
|
||
|
||
// Read the clock from KV (registers CacheKey::Channel { key: "clock" })
|
||
let clock = cx
|
||
.kv_run("clock")
|
||
.await
|
||
.and_then(|v| v.as_str().map(|s| s.to_string()))
|
||
.unwrap_or_else(|| "—".to_string());
|
||
|
||
let new_todo = cx.signal("newTodo", global());
|
||
|
||
let todo_count = todos.len();
|
||
let done_count = todos.iter().filter(|t| t.completed).count();
|
||
|
||
Ok(html! {
|
||
div.todo-app {
|
||
h1 { "LiveVue.rs — Todo List" }
|
||
|
||
nav {
|
||
span.nav-current { "Todos" }
|
||
" | "
|
||
button.nav-link
|
||
data-on:click="$tree = {page: 'About'}" {
|
||
"About"
|
||
}
|
||
}
|
||
|
||
p.clock { "🕐 " (clock) }
|
||
|
||
div.add-form {
|
||
input
|
||
type="text"
|
||
placeholder="What needs to be done?"
|
||
data-bind="newTodo"
|
||
;
|
||
button
|
||
data-on:click="@post('/action/add_todo')"
|
||
data-attr=(format!("{{disabled: {}.trim() === ''}}", new_todo.val)) {
|
||
"Add"
|
||
}
|
||
}
|
||
|
||
p.stats {
|
||
(format!("{} items, {} completed", todo_count, done_count))
|
||
}
|
||
|
||
ul.todo-list {
|
||
@for todo in &todos {
|
||
(todo_item(cx, todo))
|
||
}
|
||
}
|
||
|
||
@if todos.is_empty() {
|
||
p.empty { "No todos yet. Add one above!" }
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
fn todo_item(cx: &mut RenderContext, todo: &Todo) -> Markup {
|
||
let _editing = cx.signal("editing", mangle(todo.id).client_only());
|
||
|
||
let title_class = if todo.completed {
|
||
"todo-title completed"
|
||
} else {
|
||
"todo-title"
|
||
};
|
||
|
||
html! {
|
||
li.todo-item id=(format!("todo-{}", todo.id)) {
|
||
input
|
||
type="checkbox"
|
||
checked[todo.completed]
|
||
data-on:click=(format!("@post('/action/toggle_todo?id={}')", todo.id))
|
||
;
|
||
span class=(title_class) { (todo.title) }
|
||
button.delete-btn
|
||
data-on:click=(format!("@post('/action/delete_todo?id={}')", todo.id)) {
|
||
"×"
|
||
}
|
||
}
|
||
}
|
||
} |