todomvc.rs 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. #![allow(non_upper_case_globals, non_snake_case)]
  2. use dioxus_core as dioxus;
  3. use dioxus_core::prelude::*;
  4. use dioxus_core_macro::*;
  5. use dioxus_hooks::*;
  6. use dioxus_html as dioxus_elements;
  7. use std::collections::HashMap;
  8. fn main() {
  9. dioxus_desktop::launch(App)
  10. }
  11. #[derive(PartialEq)]
  12. pub enum FilterState {
  13. All,
  14. Active,
  15. Completed,
  16. }
  17. #[derive(Debug, PartialEq)]
  18. pub struct TodoItem {
  19. pub id: u32,
  20. pub checked: bool,
  21. pub contents: String,
  22. }
  23. pub type Todos = HashMap<u32, TodoItem>;
  24. pub static App: Component = |cx| {
  25. // Share our TodoList to the todos themselves
  26. use_provide_state(cx, Todos::new);
  27. // Save state for the draft, filter
  28. let draft = use_state(&cx, || "".to_string());
  29. let filter = use_state(&cx, || FilterState::All);
  30. let mut todo_id = use_state(&cx, || 0);
  31. // Consume the todos
  32. let todos = use_shared_state::<Todos>(cx)?;
  33. // Filter the todos based on the filter state
  34. let mut filtered_todos = todos
  35. .read()
  36. .iter()
  37. .filter(|(_, item)| match *filter {
  38. FilterState::All => true,
  39. FilterState::Active => !item.checked,
  40. FilterState::Completed => item.checked,
  41. })
  42. .map(|f| *f.0)
  43. .collect::<Vec<_>>();
  44. filtered_todos.sort_unstable();
  45. // Define the actions to manage the todolist
  46. let mut submit_todo = move || {
  47. if !draft.is_empty() {
  48. todos.write().insert(
  49. *todo_id,
  50. TodoItem {
  51. id: *todo_id,
  52. checked: false,
  53. contents: draft.get().clone(),
  54. },
  55. );
  56. todo_id += 1;
  57. draft.set("".to_string());
  58. }
  59. };
  60. let clear_completed = move || {
  61. todos.write().retain(|_, todo| todo.checked == false);
  62. };
  63. // Some assists in actually rendering the content
  64. let show_clear_completed = todos.read().values().any(|todo| todo.checked);
  65. let items_left = filtered_todos.len();
  66. let item_text = match items_left {
  67. 1 => "item",
  68. _ => "items",
  69. };
  70. cx.render(rsx!{
  71. section { class: "todoapp"
  72. style { {[include_str!("./todomvc.css")]} }
  73. div {
  74. header { class: "header"
  75. h1 {"todos"}
  76. input {
  77. class: "new-todo"
  78. placeholder: "What needs to be done?"
  79. value: "{draft}"
  80. autofocus: "true"
  81. oninput: move |evt| draft.set(evt.value)
  82. onkeydown: move |evt| {
  83. if evt.key == "Enter" {
  84. submit_todo();
  85. }
  86. }
  87. }
  88. }
  89. ul { class: "todo-list",
  90. filtered_todos.iter().map(|id| rsx!(TodoEntry { key: "{id}", id: *id }))
  91. }
  92. (!todos.read().is_empty()).then(|| rsx!(
  93. footer { class: "footer",
  94. span { class: "todo-count" strong {"{items_left} "} span {"{item_text} left"} }
  95. ul { class: "filters"
  96. li { class: "All", a { onclick: move |_| filter.set(FilterState::All), "All" }}
  97. li { class: "Active", a { onclick: move |_| filter.set(FilterState::Active), "Active" }}
  98. li { class: "Completed", a { onclick: move |_| filter.set(FilterState::Completed), "Completed" }}
  99. }
  100. (show_clear_completed).then(|| rsx!(
  101. button { class: "clear-completed", onclick: move |_| clear_completed(),
  102. "Clear completed"
  103. }
  104. ))
  105. }
  106. ))
  107. }
  108. }
  109. footer { class: "info"
  110. p {"Double-click to edit a todo"}
  111. p { "Created by ", a { "jkelleyrtp", href: "http://github.com/jkelleyrtp/" }}
  112. p { "Part of ", a { "TodoMVC", href: "http://todomvc.com" }}
  113. }
  114. })
  115. };
  116. #[derive(PartialEq, Props)]
  117. pub struct TodoEntryProps {
  118. id: u32,
  119. }
  120. pub fn TodoEntry((cx, props): Scope<TodoEntryProps>) -> Element {
  121. let todos = use_shared_state::<Todos>(cx)?;
  122. let _todos = todos.read();
  123. let todo = _todos.get(&cx.props.id)?;
  124. let is_editing = use_state(&cx, || false);
  125. let completed = if todo.checked { "completed" } else { "" };
  126. cx.render(rsx!{
  127. li { class: "{completed}"
  128. div { class: "view"
  129. input { class: "toggle" r#type: "checkbox" id: "cbg-{todo.id}" checked: "{todo.checked}"
  130. onchange: move |evt| {
  131. if let Some(todo) = todos.write().get_mut(&cx.props.id) {
  132. todo.checked = evt.value.parse().unwrap()
  133. }
  134. }
  135. }
  136. label { r#for: "cbg-{todo.id}" pointer_events: "none"
  137. "{todo.contents}"
  138. }
  139. {is_editing.then(|| rsx!{
  140. input { value: "{todo.contents}"
  141. oninput: move |evt| {
  142. if let Some(todo) = todos.write().get_mut(&cx.props.id) {
  143. todo.contents = evt.value
  144. }
  145. },
  146. }
  147. })}
  148. }
  149. }
  150. })
  151. }