diff --git a/web/src/app_state.rs b/web/src/app_state.rs index d2a1b4a..b4e900d 100644 --- a/web/src/app_state.rs +++ b/web/src/app_state.rs @@ -53,7 +53,7 @@ impl AppState { #[derive(Debug)] pub enum Message { - InitRecipeCounts(BTreeMap), + ResetRecipeCounts, UpdateRecipeCount(String, usize), InitExtras(BTreeSet<(String, String)>), AddExtra(String, String), @@ -64,6 +64,7 @@ pub enum Message { RemoveRecipe(String), SetStaples(Option), SetCategoryMap(String), + ResetInventory, UpdateCategories, InitFilteredIngredient(BTreeSet), AddFilteredIngredient(IngredientKey), @@ -105,6 +106,7 @@ fn filter_recipes( None => Ok((None, None)), } } + impl StateMachine { async fn load_state(store: HttpStore, original: &Signal) { let mut state = original.get().as_ref().clone(); @@ -184,7 +186,11 @@ impl MessageMapper for StateMachine { fn map<'ctx>(&self, cx: Scope<'ctx>, msg: Message, original: &'ctx Signal) { let mut original_copy = original.get().as_ref().clone(); match msg { - Message::InitRecipeCounts(map) => { + Message::ResetRecipeCounts => { + let mut map = BTreeMap::new(); + for (id, _) in original_copy.recipes.iter() { + map.insert(id.clone(), 0); + } original_copy.recipe_counts = map; } Message::UpdateRecipeCount(id, count) => { @@ -251,6 +257,11 @@ impl MessageMapper for StateMachine { }; }); } + Message::ResetInventory => { + original_copy.filtered_ingredients = BTreeSet::new(); + original_copy.modified_amts = BTreeMap::new(); + original_copy.extras = BTreeSet::new(); + } Message::InitFilteredIngredient(set) => { original_copy.filtered_ingredients = set; } diff --git a/web/src/components/recipe_plan.rs b/web/src/components/recipe_plan.rs index 15aa500..2d8c4b6 100644 --- a/web/src/components/recipe_plan.rs +++ b/web/src/components/recipe_plan.rs @@ -15,7 +15,6 @@ use recipes::Recipe; use sycamore::prelude::*; use tracing::instrument; -use crate::app_state; use crate::app_state::{Message, StateHandler}; use crate::components::recipe_selection::*; @@ -54,10 +53,10 @@ pub fn RecipePlan<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> Vie view ! {cx, tr { Keyed( iterable=r, - view=|cx, sig| { + view=move |cx, sig| { let title = create_memo(cx, move || sig.get().1.title.clone()); view! {cx, - td { RecipeSelection(i=sig.get().0.to_owned(), title=title) } + td { RecipeSelection(i=sig.get().0.to_owned(), title=title, sh=sh) } } }, key=|sig| sig.get().0.to_owned(), @@ -72,8 +71,7 @@ pub fn RecipePlan<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> Vie refresh_click.set(toggle); }) input(type="button", value="Clear All", on:click=move |_| { - let state = app_state::State::get_from_context(cx); - state.reset_recipe_counts(); + sh.dispatch(cx, Message::ResetRecipeCounts); }) input(type="button", value="Save Plan", on:click=move |_| { // Poor man's click event signaling. diff --git a/web/src/components/recipe_selection.rs b/web/src/components/recipe_selection.rs index 7a2e0ad..8375027 100644 --- a/web/src/components/recipe_selection.rs +++ b/web/src/components/recipe_selection.rs @@ -16,24 +16,29 @@ use std::rc::Rc; use sycamore::prelude::*; use tracing::{debug, instrument}; -use crate::app_state; +use crate::app_state::{self, Message, StateHandler}; #[derive(Props)] pub struct RecipeCheckBoxProps<'ctx> { pub i: String, pub title: &'ctx ReadSignal, + pub sh: StateHandler<'ctx>, } #[instrument(skip(props, cx), fields( - idx=%props.i, + id=%props.i, title=%props.title.get() ))] #[component] -pub fn RecipeSelection(cx: Scope, props: RecipeCheckBoxProps) -> View { +pub fn RecipeSelection<'ctx, G: Html>( + cx: Scope<'ctx>, + props: RecipeCheckBoxProps<'ctx>, +) -> View { + let RecipeCheckBoxProps { i, title, sh } = props; let state = app_state::State::get_from_context(cx); // This is total hack but it works around the borrow issues with // the `view!` macro. - let id = Rc::new(props.i); + let id = Rc::new(i); let count = create_signal( cx, format!( @@ -52,7 +57,7 @@ pub fn RecipeSelection(cx: Scope, props: RecipeCheckBoxProps) -> View(cx: Scope, props: RecipeCheckBoxProps) -> View( #[instrument(skip_all)] #[component] pub fn ShoppingList<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View { - let filtered_keys = sh.get_selector(cx, |state| state.get().filtered_ingredients.clone()); - let ingredients_map = create_rc_signal(BTreeMap::new()); let show_staples = create_signal(cx, true); let save_click = create_signal(cx, ()); - create_effect(cx, { - let state = crate::app_state::State::get_from_context(cx); - let ingredients_map = ingredients_map.clone(); - move || { - ingredients_map.set(state.get_shopping_list(*show_staples.get())); - } - }); - debug!(ingredients_map=?ingredients_map.get_untracked()); - let ingredients = create_memo(cx, { - let filtered_keys = filtered_keys.clone(); - let ingredients_map = ingredients_map.clone(); - move || { - let mut ingredients = Vec::new(); - // This has the effect of sorting the ingredients by category - for (_, ingredients_list) in ingredients_map.get().iter() { - for (i, recipes) in ingredients_list.iter() { - if !filtered_keys.get().contains(&i.key()) { - ingredients.push((i.key(), (i.clone(), recipes.clone()))); - } - } - } - ingredients - } - }); - let table_view = create_signal(cx, View::empty()); - create_effect(cx, { - let state = crate::app_state::State::get_from_context(cx); - move || { - if (ingredients.get().len() > 0) || (state.extras.get().len() > 0) { - table_view.set(make_shopping_table(cx, sh, show_staples)); - } else { - table_view.set(View::empty()); - } - } - }); create_effect(cx, move || { save_click.track(); info!("Registering save request for inventory"); sh.dispatch(cx, Message::SaveState); }); - let state = crate::app_state::State::get_from_context(cx); view! {cx, h1 { "Shopping List " } label(for="show_staples_cb") { "Show staples" } input(id="show_staples_cb", type="checkbox", bind:checked=show_staples) - (table_view.get().as_ref().clone()) + (make_shopping_table(cx, sh, show_staples)) input(type="button", value="Add Item", class="no-print", on:click=move |_| { - let mut cloned_extras: Vec<(RcSignal, RcSignal)> = (*state.extras.get()).iter().map(|(_, tpl)| tpl.clone()).collect(); - cloned_extras.push((create_rc_signal("".to_owned()), create_rc_signal("".to_owned()))); - state.extras.set(cloned_extras.drain(0..).enumerate().collect()); + sh.dispatch(cx, Message::AddExtra(String::new(), String::new())); }) - input(type="button", value="Reset", class="no-print", on:click={ - //let state = crate::app_state::State::get_from_context(cx); - move |_| { - // FIXME(jwall): This should be an event. - // // TODO(jwall): We should actually pop up a modal here or use a different set of items. - // ingredients_map.set(state.get_shopping_list(*show_staples.get())); - // // clear the filter_signal - // filtered_keys.set(BTreeSet::new()); - // state.modified_amts.set(BTreeMap::new()); - // state.extras.set(Vec::new()); - } + input(type="button", value="Reset", class="no-print", on:click=move |_| { + sh.dispatch(cx, Message::ResetInventory); }) input(type="button", value="Save", class="no-print", on:click=|_| { save_click.trigger_subscribers();