From 1a188f45aa345f420f72928e462738531098649d Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Thu, 10 Feb 2022 21:31:25 -0500 Subject: [PATCH] Rudimentary shopping list table. --- recipes/src/lib.rs | 2 +- web/src/components/shopping.rs | 74 +++++++++++++++++++++++++++++----- web/src/service.rs | 14 +++++++ web/src/web.rs | 4 +- 4 files changed, 80 insertions(+), 14 deletions(-) diff --git a/recipes/src/lib.rs b/recipes/src/lib.rs index 3c69921..08615a3 100644 --- a/recipes/src/lib.rs +++ b/recipes/src/lib.rs @@ -167,7 +167,7 @@ impl Step { /// Unique identifier for an Ingredient. Ingredients are identified by name, form, /// and measurement type. (Volume, Count, Weight) -#[derive(PartialEq, PartialOrd, Eq, Ord)] +#[derive(PartialEq, PartialOrd, Eq, Ord, Clone)] pub struct IngredientKey(String, Option, String); /// Ingredient in a recipe. The `name` and `form` fields with the measurement type diff --git a/web/src/components/shopping.rs b/web/src/components/shopping.rs index fe360b8..bca6301 100644 --- a/web/src/components/shopping.rs +++ b/web/src/components/shopping.rs @@ -15,8 +15,33 @@ use crate::console_log; use crate::service::AppService; use std::rc::Rc; +use recipes::{Ingredient, IngredientAccumulator, IngredientKey}; use sycamore::{context::use_context, prelude::*}; +struct RecipeCheckBoxProps { + i: usize, + title: String, +} + +#[component(RecipeCheckBox)] +fn recipe_check_box(props: RecipeCheckBoxProps) -> View { + let app_service = use_context::(); + // This is total hack but it works around the borrow issues with + // the `view!` macro. + let i = props.i; + let id_as_str = Rc::new(format!("{}", i)); + let id_cloned = id_as_str.clone(); + let id_cloned_2 = id_as_str.clone(); + view! { + input(type="checkbox", name="recipe_id", value=id_as_str.clone(), on:click=move |_| { + let mut app_service = app_service.clone(); + console_log!("clicked checkbox for id {}", id_cloned); + app_service.add_recipe_by_index(i); + }) + label(for=id_cloned_2) { (props.title) } + } +} + #[component(RecipeSelector)] pub fn recipe_selector() -> View { let app_service = use_context::(); @@ -28,16 +53,9 @@ pub fn recipe_selector() -> View { Keyed(KeyedProps{ iterable: titles, template: |(i, title)| { - // This is total hack but it works around the borrow issues with - // the `view!` macro. - let id_as_str = Rc::new(format!("{}", i)); - let id_cloned = id_as_str.clone(); - let id_cloned_2 = id_as_str.clone(); view! { - input(type="checkbox", name="recipe_id", value=id_as_str.clone(), on:click=move |_| { - console_log!("clicked checkbox for id {}", id_cloned); - }) - label(for=id_cloned_2) { (title) } } + RecipeCheckBox(RecipeCheckBoxProps{i: i, title: title}) + } }, key: |(i, title)| (*i, title.clone()), }) @@ -45,12 +63,48 @@ pub fn recipe_selector() -> View { } } -#[component(MenuView)] +#[component(ShoppingList)] +fn shopping_list() -> View { + let app_service = use_context::(); + let ingredients = create_memo(move || { + let mut acc = IngredientAccumulator::new(); + for r in app_service.get_menu_list().get().iter() { + acc.accumulate_from(r); + } + acc.ingredients() + .iter() + .map(|(k, v)| (k.clone(), v.clone())) + .collect::>() + }); + + view! { + table(class="shopping_list") { + tr { + th { "Quantity" } + th { "Ingredient" } + } + Indexed(IndexedProps{ + iterable: ingredients, + template: |(_k, i)| { + view! { + tr { + td { (i.amt) } + td { (i.name) } + } + } + }, + }) + } + } +} + +#[component(ShoppingView)] pub fn shopping_view() -> View { view! { h1 { "Select your recipes" } RecipeSelector() + ShoppingList() } } diff --git a/web/src/service.rs b/web/src/service.rs index b0c3116..8d25852 100644 --- a/web/src/service.rs +++ b/web/src/service.rs @@ -11,6 +11,8 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +use std::rc::Rc; + use crate::{console_debug, console_error}; use reqwasm::http; @@ -22,12 +24,14 @@ use recipes::{parse, Recipe}; pub struct AppService { // TODO(jwall): Should each Recipe also be a Signal? recipes: Signal>, + menu_list: Signal>, } impl AppService { pub fn new() -> Self { Self { recipes: Signal::new(Vec::new()), + menu_list: Signal::new(Vec::new()), } } @@ -60,6 +64,16 @@ impl AppService { } } + pub fn get_menu_list(&self) -> Signal> { + self.menu_list.clone() + } + + pub fn add_recipe_by_index(&mut self, i: usize) { + let mut v = (*self.menu_list.get()).clone(); + v.push(self.recipes.get()[i].1.clone()); + self.menu_list.set(v); + } + pub fn get_recipes(&self) -> Signal> { self.recipes.clone() } diff --git a/web/src/web.rs b/web/src/web.rs index ffe030c..1ab96fa 100644 --- a/web/src/web.rs +++ b/web/src/web.rs @@ -1,5 +1,3 @@ -use std::rc::Rc; - // Copyright 2022 Jeremy Wall // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -45,7 +43,7 @@ fn route_switch(route: ReadSignal) -> View { RecipeView(*idx) }, AppRoutes::Menu => view! { - "TODO!!" + ShoppingView() }, AppRoutes::NotFound => view! { "NotFound"