// Reactive Todo App
// Demonstrates: Complex state management, lists, filtering, reactivity
use windjammer_ui.prelude.*
use windjammer_ui.vdom.{VElement, VNode, VText}
use windjammer_ui.reactivity.Signal
struct Todo {
id: int,
text: String,
completed: bool,
}
@component
struct TodoApp {
todos: Signal<Vec<Todo>>,
input: Signal<String>,
filter: Signal<String> // "all", "active", "completed",
next_id: Signal<int>,
}
impl Component for TodoApp {
fn render() -> VNode {
let current_input = input.get()
let filtered_todos = get_filtered_todos()
let active_count = get_active_count()
VElement.new("div")
.attr("class", "todo-app")
.child(VNode.Element(
VElement.new("h1")
.child(VNode.Text(VText.new("Windjammer Todos")))
))
.child(VNode.Element(
VElement.new("input")
.attr("type", "text")
.attr("placeholder", "What needs to be done?")
.attr("value", current_input)
.attr("oninput", "update_input")
))
.child(render_todo_list(filtered_todos))
.child(VNode.Text(VText.new("{active_count} items left")))
.into()
}
}
impl TodoApp {
fn new() -> Self {
Self {
todos: Signal.new(Vec.new()),
input: Signal.new(String.new()),
filter: Signal.new("all".to_string()),
next_id: Signal.new(0),
}
}
fn render_todo_list(todos: Vec<Todo>) -> VNode {
let items = todos.iter().map(|todo| {
VElement.new("li")
.child(VNode.Text(VText.new(todo.text.clone())))
.into()
}).collect()
VElement.new("ul").children(items).into()
}
fn add_todo() {
let text = input.get()
if text.len() > 0 {
let id = next_id.get()
todos.update(|list| {
list.push(Todo { id, text: text.clone(), completed: false })
})
next_id.update(|n| *n += 1)
input.set(String.new())
}
}
fn get_filtered_todos() -> Vec<Todo> {
todos.get()
}
fn get_active_count() -> int {
todos.get().iter().filter(|t| !t.completed).count()
}
fn update_input(value: String) {
input.set(value)
}
}
fn main() {
let app = TodoApp.new()
mount("#app", app)
}