Rudimentary shopping list table.

This commit is contained in:
Jeremy Wall 2022-02-10 21:31:25 -05:00
parent 1b969f9355
commit 1a188f45aa
4 changed files with 80 additions and 14 deletions

View File

@ -167,7 +167,7 @@ impl Step {
/// Unique identifier for an Ingredient. Ingredients are identified by name, form, /// Unique identifier for an Ingredient. Ingredients are identified by name, form,
/// and measurement type. (Volume, Count, Weight) /// and measurement type. (Volume, Count, Weight)
#[derive(PartialEq, PartialOrd, Eq, Ord)] #[derive(PartialEq, PartialOrd, Eq, Ord, Clone)]
pub struct IngredientKey(String, Option<String>, String); pub struct IngredientKey(String, Option<String>, String);
/// Ingredient in a recipe. The `name` and `form` fields with the measurement type /// Ingredient in a recipe. The `name` and `form` fields with the measurement type

View File

@ -15,8 +15,33 @@ use crate::console_log;
use crate::service::AppService; use crate::service::AppService;
use std::rc::Rc; use std::rc::Rc;
use recipes::{Ingredient, IngredientAccumulator, IngredientKey};
use sycamore::{context::use_context, prelude::*}; use sycamore::{context::use_context, prelude::*};
struct RecipeCheckBoxProps {
i: usize,
title: String,
}
#[component(RecipeCheckBox<G>)]
fn recipe_check_box(props: RecipeCheckBoxProps) -> View<G> {
let app_service = use_context::<AppService>();
// 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<G>)] #[component(RecipeSelector<G>)]
pub fn recipe_selector() -> View<G> { pub fn recipe_selector() -> View<G> {
let app_service = use_context::<AppService>(); let app_service = use_context::<AppService>();
@ -28,16 +53,9 @@ pub fn recipe_selector() -> View<G> {
Keyed(KeyedProps{ Keyed(KeyedProps{
iterable: titles, iterable: titles,
template: |(i, title)| { 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! { view! {
input(type="checkbox", name="recipe_id", value=id_as_str.clone(), on:click=move |_| { RecipeCheckBox(RecipeCheckBoxProps{i: i, title: title})
console_log!("clicked checkbox for id {}", id_cloned); }
})
label(for=id_cloned_2) { (title) } }
}, },
key: |(i, title)| (*i, title.clone()), key: |(i, title)| (*i, title.clone()),
}) })
@ -45,12 +63,48 @@ pub fn recipe_selector() -> View<G> {
} }
} }
#[component(MenuView<G>)] #[component(ShoppingList<G>)]
fn shopping_list() -> View<G> {
let app_service = use_context::<AppService>();
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::<Vec<(IngredientKey, Ingredient)>>()
});
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<G>)]
pub fn shopping_view() -> View<G> { pub fn shopping_view() -> View<G> {
view! { view! {
h1 { h1 {
"Select your recipes" "Select your recipes"
} }
RecipeSelector() RecipeSelector()
ShoppingList()
} }
} }

View File

@ -11,6 +11,8 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::rc::Rc;
use crate::{console_debug, console_error}; use crate::{console_debug, console_error};
use reqwasm::http; use reqwasm::http;
@ -22,12 +24,14 @@ use recipes::{parse, Recipe};
pub struct AppService { pub struct AppService {
// TODO(jwall): Should each Recipe also be a Signal? // TODO(jwall): Should each Recipe also be a Signal?
recipes: Signal<Vec<(usize, Recipe)>>, recipes: Signal<Vec<(usize, Recipe)>>,
menu_list: Signal<Vec<Recipe>>,
} }
impl AppService { impl AppService {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
recipes: Signal::new(Vec::new()), recipes: Signal::new(Vec::new()),
menu_list: Signal::new(Vec::new()),
} }
} }
@ -60,6 +64,16 @@ impl AppService {
} }
} }
pub fn get_menu_list(&self) -> Signal<Vec<Recipe>> {
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<Vec<(usize, Recipe)>> { pub fn get_recipes(&self) -> Signal<Vec<(usize, Recipe)>> {
self.recipes.clone() self.recipes.clone()
} }

View File

@ -1,5 +1,3 @@
use std::rc::Rc;
// Copyright 2022 Jeremy Wall // Copyright 2022 Jeremy Wall
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
@ -45,7 +43,7 @@ fn route_switch<G: Html>(route: ReadSignal<AppRoutes>) -> View<G> {
RecipeView(*idx) RecipeView(*idx)
}, },
AppRoutes::Menu => view! { AppRoutes::Menu => view! {
"TODO!!" ShoppingView()
}, },
AppRoutes::NotFound => view! { AppRoutes::NotFound => view! {
"NotFound" "NotFound"