todomvc.rs 5.6 KB

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