mirror of
https://github.com/yewstack/yew.git
synced 2025-12-08 21:26:25 +00:00
Add todomvc demo to the showcase
This commit is contained in:
parent
5043c53e91
commit
f542b7a0fe
@ -16,3 +16,4 @@ mount_point = { path = "sub/mount_point" }
|
||||
npm_and_rest = { path = "sub/npm_and_rest" }
|
||||
textarea = { path = "sub/textarea" }
|
||||
timer = { path = "sub/timer" }
|
||||
todomvc = { path = "sub/todomvc" }
|
||||
|
||||
@ -13,6 +13,7 @@ extern crate mount_point;
|
||||
extern crate npm_and_rest;
|
||||
extern crate textarea;
|
||||
extern crate timer;
|
||||
extern crate todomvc;
|
||||
|
||||
use yew::prelude::*;
|
||||
use yew::services::console::ConsoleService;
|
||||
@ -35,6 +36,7 @@ use npm_and_rest::gravatar::GravatarService;
|
||||
use npm_and_rest::ccxt::CcxtService;
|
||||
use textarea::Model as Textarea;
|
||||
use timer::Model as Timer;
|
||||
use todomvc::Model as Todomvc;
|
||||
|
||||
struct Context {
|
||||
console: ConsoleService,
|
||||
@ -121,6 +123,7 @@ enum Scene {
|
||||
NpmAndRest,
|
||||
Textarea,
|
||||
Timer,
|
||||
Todomvc,
|
||||
}
|
||||
|
||||
enum Msg {
|
||||
@ -161,6 +164,7 @@ impl Renderable<Context, Scene> for Scene {
|
||||
<button onclick=|_| Msg::SwitchTo(Scene::NpmAndRest),>{ "NpmAndRest" }</button>
|
||||
<button onclick=|_| Msg::SwitchTo(Scene::Textarea),>{ "Textarea" }</button>
|
||||
<button onclick=|_| Msg::SwitchTo(Scene::Timer),>{ "Timer" }</button>
|
||||
<button onclick=|_| Msg::SwitchTo(Scene::Todomvc),>{ "Todomvc" }</button>
|
||||
{ self.view_scene() }
|
||||
}
|
||||
}
|
||||
@ -229,6 +233,11 @@ impl Scene {
|
||||
<Timer: />
|
||||
}
|
||||
}
|
||||
Scene::Todomvc => {
|
||||
html! {
|
||||
<Todomvc: />
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,4 +8,4 @@ strum = "0.8.0"
|
||||
strum_macros = "0.8.0"
|
||||
serde = "1"
|
||||
serde_derive = "1"
|
||||
yew = { path = "../.." }
|
||||
yew = { path = "../../../.." }
|
||||
|
||||
348
examples/showcase/sub/todomvc/src/lib.rs
Normal file
348
examples/showcase/sub/todomvc/src/lib.rs
Normal file
@ -0,0 +1,348 @@
|
||||
#![recursion_limit="128"]
|
||||
|
||||
extern crate strum;
|
||||
#[macro_use]
|
||||
extern crate strum_macros;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
#[macro_use]
|
||||
extern crate yew;
|
||||
|
||||
use strum::IntoEnumIterator;
|
||||
use yew::prelude::*;
|
||||
use yew::format::Json;
|
||||
use yew::services::storage::StorageService;
|
||||
|
||||
const KEY: &'static str = "yew.todomvc.self";
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Model {
|
||||
entries: Vec<Entry>,
|
||||
filter: Filter,
|
||||
value: String,
|
||||
edit_value: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct Entry {
|
||||
description: String,
|
||||
completed: bool,
|
||||
editing: bool,
|
||||
}
|
||||
|
||||
pub enum Msg {
|
||||
Add,
|
||||
Edit(usize),
|
||||
Update(String),
|
||||
UpdateEdit(String),
|
||||
Remove(usize),
|
||||
SetFilter(Filter),
|
||||
ToggleAll,
|
||||
ToggleEdit(usize),
|
||||
Toggle(usize),
|
||||
ClearCompleted,
|
||||
Nope,
|
||||
}
|
||||
|
||||
impl<CTX> Component<CTX> for Model
|
||||
where
|
||||
CTX: AsMut<StorageService>,
|
||||
{
|
||||
type Msg = Msg;
|
||||
type Properties = ();
|
||||
|
||||
fn create(_: Self::Properties, context: &mut Env<CTX, Self>) -> Self {
|
||||
let entries = {
|
||||
if let Json(Ok(restored_model)) = context.as_mut().restore(KEY) {
|
||||
restored_model
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
};
|
||||
Model {
|
||||
entries,
|
||||
filter: Filter::All,
|
||||
value: "".into(),
|
||||
edit_value: "".into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Msg, context: &mut Env<CTX, Self>) -> ShouldRender {
|
||||
match msg {
|
||||
Msg::Add => {
|
||||
let entry = Entry {
|
||||
description: self.value.clone(),
|
||||
completed: false,
|
||||
editing: false,
|
||||
};
|
||||
self.entries.push(entry);
|
||||
self.value = "".to_string();
|
||||
}
|
||||
Msg::Edit(idx) => {
|
||||
let edit_value = self.edit_value.clone();
|
||||
self.complete_edit(idx, edit_value);
|
||||
self.edit_value = "".to_string();
|
||||
}
|
||||
Msg::Update(val) => {
|
||||
println!("Input: {}", val);
|
||||
self.value = val;
|
||||
}
|
||||
Msg::UpdateEdit(val) => {
|
||||
println!("Input: {}", val);
|
||||
self.edit_value = val;
|
||||
}
|
||||
Msg::Remove(idx) => {
|
||||
self.remove(idx);
|
||||
}
|
||||
Msg::SetFilter(filter) => {
|
||||
self.filter = filter;
|
||||
}
|
||||
Msg::ToggleEdit(idx) => {
|
||||
self.edit_value = self.entries[idx].description.clone();
|
||||
self.toggle_edit(idx);
|
||||
}
|
||||
Msg::ToggleAll => {
|
||||
let status = !self.is_all_completed();
|
||||
self.toggle_all(status);
|
||||
}
|
||||
Msg::Toggle(idx) => {
|
||||
self.toggle(idx);
|
||||
}
|
||||
Msg::ClearCompleted => {
|
||||
self.clear_completed();
|
||||
}
|
||||
Msg::Nope => {}
|
||||
}
|
||||
context.as_mut().store(KEY, Json(&self.entries));
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl<CTX> Renderable<CTX, Model> for Model
|
||||
where
|
||||
CTX: AsMut<StorageService> + 'static,
|
||||
{
|
||||
fn view(&self) -> Html<CTX, Self> {
|
||||
html! {
|
||||
<div class="todomvc-wrapper",>
|
||||
<section class="todoapp",>
|
||||
<header class="header",>
|
||||
<h1>{ "todos" }</h1>
|
||||
{ self.view_input() }
|
||||
</header>
|
||||
<section class="main",>
|
||||
<input class="toggle-all", type="checkbox", checked=self.is_all_completed(), onclick=|_| Msg::ToggleAll, />
|
||||
<ul class="todo-list",>
|
||||
{ for self.entries.iter().filter(|e| self.filter.fit(e)).enumerate().map(view_entry) }
|
||||
</ul>
|
||||
</section>
|
||||
<footer class="footer",>
|
||||
<span class="todo-count",>
|
||||
<strong>{ self.total() }</strong>
|
||||
{ " item(s) left" }
|
||||
</span>
|
||||
<ul class="filters",>
|
||||
{ for Filter::iter().map(|flt| self.view_filter(flt)) }
|
||||
</ul>
|
||||
<button class="clear-completed", onclick=|_| Msg::ClearCompleted,>
|
||||
{ format!("Clear completed ({})", self.total_completed()) }
|
||||
</button>
|
||||
</footer>
|
||||
</section>
|
||||
<footer class="info",>
|
||||
<p>{ "Double-click to edit a todo" }</p>
|
||||
<p>{ "Written by " }<a href="https://github.com/DenisKolodin/", target="_blank",>{ "Denis Kolodin" }</a></p>
|
||||
<p>{ "Part of " }<a href="http://todomvc.com/", target="_blank",>{ "TodoMVC" }</a></p>
|
||||
</footer>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Model {
|
||||
fn view_filter<CTX>(&self, filter: Filter) -> Html<CTX, Model>
|
||||
where
|
||||
CTX: AsMut<StorageService> + 'static,
|
||||
{
|
||||
let flt = filter.clone();
|
||||
html! {
|
||||
<li>
|
||||
<a class=if self.filter == flt { "selected" } else { "not-selected" },
|
||||
href=&flt,
|
||||
onclick=move |_| Msg::SetFilter(flt.clone()),>
|
||||
{ filter }
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
|
||||
fn view_input<CTX>(&self) -> Html<CTX, Model>
|
||||
where
|
||||
CTX: AsMut<StorageService> + 'static,
|
||||
{
|
||||
html! {
|
||||
// You can use standard Rust comments. One line:
|
||||
// <li></li>
|
||||
<input class="new-todo",
|
||||
placeholder="What needs to be done?",
|
||||
value=&self.value,
|
||||
oninput=|e: InputData| Msg::Update(e.value),
|
||||
onkeypress=|e: KeyData| {
|
||||
if e.key == "Enter" { Msg::Add } else { Msg::Nope }
|
||||
}, />
|
||||
/* Or multiline:
|
||||
<ul>
|
||||
<li></li>
|
||||
</ul>
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn view_entry<CTX>((idx, entry): (usize, &Entry)) -> Html<CTX, Model>
|
||||
where
|
||||
CTX: AsMut<StorageService> + 'static,
|
||||
{
|
||||
html! {
|
||||
<li class=if entry.editing == true { "editing" } else { "" },>
|
||||
<div class="view",>
|
||||
<input class="toggle", type="checkbox", checked=entry.completed, onclick=move|_| Msg::Toggle(idx), />
|
||||
<label ondoubleclick=move|_| Msg::ToggleEdit(idx),>{ &entry.description }</label>
|
||||
<button class="destroy", onclick=move |_| Msg::Remove(idx), />
|
||||
</div>
|
||||
{ view_entry_edit_input((idx, &entry)) }
|
||||
</li>
|
||||
}
|
||||
}
|
||||
|
||||
fn view_entry_edit_input<CTX>((idx, entry): (usize, &Entry)) -> Html<CTX, Model>
|
||||
where
|
||||
CTX: AsMut<StorageService> + 'static,
|
||||
{
|
||||
if entry.editing == true {
|
||||
html! {
|
||||
<input class="edit",
|
||||
type="text",
|
||||
value=&entry.description,
|
||||
oninput=|e: InputData| Msg::UpdateEdit(e.value),
|
||||
onblur=move|_| Msg::Edit(idx),
|
||||
onkeypress=move |e: KeyData| {
|
||||
if e.key == "Enter" { Msg::Edit(idx) } else { Msg::Nope }
|
||||
}, />
|
||||
}
|
||||
} else {
|
||||
html! { <input type="hidden", /> }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(EnumIter, ToString, Clone, PartialEq)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub enum Filter {
|
||||
All,
|
||||
Active,
|
||||
Completed,
|
||||
}
|
||||
|
||||
impl<'a> Into<Href> for &'a Filter {
|
||||
fn into(self) -> Href {
|
||||
match *self {
|
||||
Filter::All => "#/".into(),
|
||||
Filter::Active => "#/active".into(),
|
||||
Filter::Completed => "#/completed".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Filter {
|
||||
fn fit(&self, entry: &Entry) -> bool {
|
||||
match *self {
|
||||
Filter::All => true,
|
||||
Filter::Active => !entry.completed,
|
||||
Filter::Completed => entry.completed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Model {
|
||||
fn total(&self) -> usize {
|
||||
self.entries.len()
|
||||
}
|
||||
|
||||
fn total_completed(&self) -> usize {
|
||||
self.entries.iter().filter(|e| Filter::Completed.fit(e)).count()
|
||||
}
|
||||
|
||||
fn is_all_completed(&self) -> bool {
|
||||
let mut filtered_iter = self.entries
|
||||
.iter()
|
||||
.filter(|e| self.filter.fit(e))
|
||||
.peekable();
|
||||
|
||||
if filtered_iter.peek().is_none() {
|
||||
return false;
|
||||
}
|
||||
|
||||
filtered_iter.all(|e| e.completed)
|
||||
}
|
||||
|
||||
fn toggle_all(&mut self, value: bool) {
|
||||
for entry in self.entries.iter_mut() {
|
||||
if self.filter.fit(entry) {
|
||||
entry.completed = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_completed(&mut self) {
|
||||
let entries = self.entries.drain(..)
|
||||
.filter(|e| Filter::Active.fit(e))
|
||||
.collect();
|
||||
self.entries = entries;
|
||||
}
|
||||
|
||||
fn toggle(&mut self, idx: usize) {
|
||||
let filter = self.filter.clone();
|
||||
let mut entries = self.entries
|
||||
.iter_mut()
|
||||
.filter(|e| filter.fit(e))
|
||||
.collect::<Vec<_>>();
|
||||
let entry = entries.get_mut(idx).unwrap();
|
||||
entry.completed = !entry.completed;
|
||||
}
|
||||
|
||||
fn toggle_edit(&mut self, idx: usize) {
|
||||
let filter = self.filter.clone();
|
||||
let mut entries = self.entries
|
||||
.iter_mut()
|
||||
.filter(|e| filter.fit(e))
|
||||
.collect::<Vec<_>>();
|
||||
let entry = entries.get_mut(idx).unwrap();
|
||||
entry.editing = !entry.editing;
|
||||
}
|
||||
|
||||
fn complete_edit(&mut self, idx: usize, val: String) {
|
||||
let filter = self.filter.clone();
|
||||
let mut entries = self.entries
|
||||
.iter_mut()
|
||||
.filter(|e| filter.fit(e))
|
||||
.collect::<Vec<_>>();
|
||||
let entry = entries.get_mut(idx).unwrap();
|
||||
entry.description = val;
|
||||
entry.editing = !entry.editing;
|
||||
}
|
||||
|
||||
fn remove(&mut self, idx: usize) {
|
||||
let idx = {
|
||||
let filter = self.filter.clone();
|
||||
let entries = self.entries
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|&(_, e)| filter.fit(e))
|
||||
.collect::<Vec<_>>();
|
||||
let &(idx, _) = entries.get(idx).unwrap();
|
||||
idx
|
||||
};
|
||||
self.entries.remove(idx);
|
||||
}
|
||||
}
|
||||
@ -1,227 +1,20 @@
|
||||
#![recursion_limit="128"]
|
||||
|
||||
extern crate strum;
|
||||
#[macro_use]
|
||||
extern crate strum_macros;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
#[macro_use]
|
||||
extern crate yew;
|
||||
extern crate todomvc;
|
||||
|
||||
use strum::IntoEnumIterator;
|
||||
use yew::prelude::*;
|
||||
use yew::format::Json;
|
||||
use yew::services::storage::{StorageService, Area};
|
||||
|
||||
const KEY: &'static str = "yew.todomvc.self";
|
||||
use todomvc::Model;
|
||||
|
||||
struct Context {
|
||||
storage: StorageService,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct Model {
|
||||
entries: Vec<Entry>,
|
||||
filter: Filter,
|
||||
value: String,
|
||||
edit_value: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct Entry {
|
||||
description: String,
|
||||
completed: bool,
|
||||
editing: bool,
|
||||
}
|
||||
|
||||
enum Msg {
|
||||
Add,
|
||||
Edit(usize),
|
||||
Update(String),
|
||||
UpdateEdit(String),
|
||||
Remove(usize),
|
||||
SetFilter(Filter),
|
||||
ToggleAll,
|
||||
ToggleEdit(usize),
|
||||
Toggle(usize),
|
||||
ClearCompleted,
|
||||
Nope,
|
||||
}
|
||||
|
||||
impl Component<Context> for Model {
|
||||
type Msg = Msg;
|
||||
type Properties = ();
|
||||
|
||||
fn create(_: Self::Properties, context: &mut Env<Context, Self>) -> Self {
|
||||
let entries = {
|
||||
if let Json(Ok(restored_model)) = context.storage.restore(KEY) {
|
||||
restored_model
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
};
|
||||
Model {
|
||||
entries,
|
||||
filter: Filter::All,
|
||||
value: "".into(),
|
||||
edit_value: "".into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Msg, context: &mut Env<Context, Self>) -> ShouldRender {
|
||||
match msg {
|
||||
Msg::Add => {
|
||||
let entry = Entry {
|
||||
description: self.value.clone(),
|
||||
completed: false,
|
||||
editing: false,
|
||||
};
|
||||
self.entries.push(entry);
|
||||
self.value = "".to_string();
|
||||
}
|
||||
Msg::Edit(idx) => {
|
||||
let edit_value = self.edit_value.clone();
|
||||
self.complete_edit(idx, edit_value);
|
||||
self.edit_value = "".to_string();
|
||||
}
|
||||
Msg::Update(val) => {
|
||||
println!("Input: {}", val);
|
||||
self.value = val;
|
||||
}
|
||||
Msg::UpdateEdit(val) => {
|
||||
println!("Input: {}", val);
|
||||
self.edit_value = val;
|
||||
}
|
||||
Msg::Remove(idx) => {
|
||||
self.remove(idx);
|
||||
}
|
||||
Msg::SetFilter(filter) => {
|
||||
self.filter = filter;
|
||||
}
|
||||
Msg::ToggleEdit(idx) => {
|
||||
self.edit_value = self.entries[idx].description.clone();
|
||||
self.toggle_edit(idx);
|
||||
}
|
||||
Msg::ToggleAll => {
|
||||
let status = !self.is_all_completed();
|
||||
self.toggle_all(status);
|
||||
}
|
||||
Msg::Toggle(idx) => {
|
||||
self.toggle(idx);
|
||||
}
|
||||
Msg::ClearCompleted => {
|
||||
self.clear_completed();
|
||||
}
|
||||
Msg::Nope => {}
|
||||
}
|
||||
context.storage.store(KEY, Json(&self.entries));
|
||||
true
|
||||
impl AsMut<StorageService> for Context {
|
||||
fn as_mut(&mut self) -> &mut StorageService {
|
||||
&mut self.storage
|
||||
}
|
||||
}
|
||||
|
||||
impl Renderable<Context, Model> for Model {
|
||||
fn view(&self) -> Html<Context, Self> {
|
||||
html! {
|
||||
<div class="todomvc-wrapper",>
|
||||
<section class="todoapp",>
|
||||
<header class="header",>
|
||||
<h1>{ "todos" }</h1>
|
||||
{ self.view_input() }
|
||||
</header>
|
||||
<section class="main",>
|
||||
<input class="toggle-all", type="checkbox", checked=self.is_all_completed(), onclick=|_| Msg::ToggleAll, />
|
||||
<ul class="todo-list",>
|
||||
{ for self.entries.iter().filter(|e| self.filter.fit(e)).enumerate().map(view_entry) }
|
||||
</ul>
|
||||
</section>
|
||||
<footer class="footer",>
|
||||
<span class="todo-count",>
|
||||
<strong>{ self.total() }</strong>
|
||||
{ " item(s) left" }
|
||||
</span>
|
||||
<ul class="filters",>
|
||||
{ for Filter::iter().map(|flt| self.view_filter(flt)) }
|
||||
</ul>
|
||||
<button class="clear-completed", onclick=|_| Msg::ClearCompleted,>
|
||||
{ format!("Clear completed ({})", self.total_completed()) }
|
||||
</button>
|
||||
</footer>
|
||||
</section>
|
||||
<footer class="info",>
|
||||
<p>{ "Double-click to edit a todo" }</p>
|
||||
<p>{ "Written by " }<a href="https://github.com/DenisKolodin/", target="_blank",>{ "Denis Kolodin" }</a></p>
|
||||
<p>{ "Part of " }<a href="http://todomvc.com/", target="_blank",>{ "TodoMVC" }</a></p>
|
||||
</footer>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Model {
|
||||
fn view_filter(&self, filter: Filter) -> Html<Context, Model> {
|
||||
let flt = filter.clone();
|
||||
html! {
|
||||
<li>
|
||||
<a class=if self.filter == flt { "selected" } else { "not-selected" },
|
||||
href=&flt,
|
||||
onclick=move |_| Msg::SetFilter(flt.clone()),>
|
||||
{ filter }
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
|
||||
fn view_input(&self) -> Html<Context, Model> {
|
||||
html! {
|
||||
// You can use standard Rust comments. One line:
|
||||
// <li></li>
|
||||
<input class="new-todo",
|
||||
placeholder="What needs to be done?",
|
||||
value=&self.value,
|
||||
oninput=|e: InputData| Msg::Update(e.value),
|
||||
onkeypress=|e: KeyData| {
|
||||
if e.key == "Enter" { Msg::Add } else { Msg::Nope }
|
||||
}, />
|
||||
/* Or multiline:
|
||||
<ul>
|
||||
<li></li>
|
||||
</ul>
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn view_entry((idx, entry): (usize, &Entry)) -> Html<Context, Model> {
|
||||
html! {
|
||||
<li class=if entry.editing == true { "editing" } else { "" },>
|
||||
<div class="view",>
|
||||
<input class="toggle", type="checkbox", checked=entry.completed, onclick=move|_| Msg::Toggle(idx), />
|
||||
<label ondoubleclick=move|_| Msg::ToggleEdit(idx),>{ &entry.description }</label>
|
||||
<button class="destroy", onclick=move |_| Msg::Remove(idx), />
|
||||
</div>
|
||||
{ view_entry_edit_input((idx, &entry)) }
|
||||
</li>
|
||||
}
|
||||
}
|
||||
|
||||
fn view_entry_edit_input((idx, entry): (usize, &Entry)) -> Html<Context, Model> {
|
||||
if entry.editing == true {
|
||||
html! {
|
||||
<input class="edit",
|
||||
type="text",
|
||||
value=&entry.description,
|
||||
oninput=|e: InputData| Msg::UpdateEdit(e.value),
|
||||
onblur=move|_| Msg::Edit(idx),
|
||||
onkeypress=move |e: KeyData| {
|
||||
if e.key == "Enter" { Msg::Edit(idx) } else { Msg::Nope }
|
||||
}, />
|
||||
}
|
||||
} else {
|
||||
html! { <input type="hidden", /> }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn main() {
|
||||
yew::initialize();
|
||||
let context = Context {
|
||||
@ -232,113 +25,3 @@ fn main() {
|
||||
yew::run_loop();
|
||||
}
|
||||
|
||||
#[derive(EnumIter, ToString, Clone, PartialEq)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
enum Filter {
|
||||
All,
|
||||
Active,
|
||||
Completed,
|
||||
}
|
||||
|
||||
impl<'a> Into<Href> for &'a Filter {
|
||||
fn into(self) -> Href {
|
||||
match *self {
|
||||
Filter::All => "#/".into(),
|
||||
Filter::Active => "#/active".into(),
|
||||
Filter::Completed => "#/completed".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Filter {
|
||||
fn fit(&self, entry: &Entry) -> bool {
|
||||
match *self {
|
||||
Filter::All => true,
|
||||
Filter::Active => !entry.completed,
|
||||
Filter::Completed => entry.completed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Model {
|
||||
fn total(&self) -> usize {
|
||||
self.entries.len()
|
||||
}
|
||||
|
||||
fn total_completed(&self) -> usize {
|
||||
self.entries.iter().filter(|e| Filter::Completed.fit(e)).count()
|
||||
}
|
||||
|
||||
fn is_all_completed(&self) -> bool {
|
||||
let mut filtered_iter = self.entries
|
||||
.iter()
|
||||
.filter(|e| self.filter.fit(e))
|
||||
.peekable();
|
||||
|
||||
if filtered_iter.peek().is_none() {
|
||||
return false;
|
||||
}
|
||||
|
||||
filtered_iter.all(|e| e.completed)
|
||||
}
|
||||
|
||||
fn toggle_all(&mut self, value: bool) {
|
||||
for entry in self.entries.iter_mut() {
|
||||
if self.filter.fit(entry) {
|
||||
entry.completed = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_completed(&mut self) {
|
||||
let entries = self.entries.drain(..)
|
||||
.filter(|e| Filter::Active.fit(e))
|
||||
.collect();
|
||||
self.entries = entries;
|
||||
}
|
||||
|
||||
fn toggle(&mut self, idx: usize) {
|
||||
let filter = self.filter.clone();
|
||||
let mut entries = self.entries
|
||||
.iter_mut()
|
||||
.filter(|e| filter.fit(e))
|
||||
.collect::<Vec<_>>();
|
||||
let entry = entries.get_mut(idx).unwrap();
|
||||
entry.completed = !entry.completed;
|
||||
}
|
||||
|
||||
fn toggle_edit(&mut self, idx: usize) {
|
||||
let filter = self.filter.clone();
|
||||
let mut entries = self.entries
|
||||
.iter_mut()
|
||||
.filter(|e| filter.fit(e))
|
||||
.collect::<Vec<_>>();
|
||||
let entry = entries.get_mut(idx).unwrap();
|
||||
entry.editing = !entry.editing;
|
||||
}
|
||||
|
||||
fn complete_edit(&mut self, idx: usize, val: String) {
|
||||
let filter = self.filter.clone();
|
||||
let mut entries = self.entries
|
||||
.iter_mut()
|
||||
.filter(|e| filter.fit(e))
|
||||
.collect::<Vec<_>>();
|
||||
let entry = entries.get_mut(idx).unwrap();
|
||||
entry.description = val;
|
||||
entry.editing = !entry.editing;
|
||||
}
|
||||
|
||||
fn remove(&mut self, idx: usize) {
|
||||
let idx = {
|
||||
let filter = self.filter.clone();
|
||||
let entries = self.entries
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|&(_, e)| filter.fit(e))
|
||||
.collect::<Vec<_>>();
|
||||
let &(idx, _) = entries.get(idx).unwrap();
|
||||
idx
|
||||
};
|
||||
self.entries.remove(idx);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user