mirror of
https://github.com/zaphar/kitchen.git
synced 2025-07-22 19:40:14 -04:00
MealPlan: Show shopping list and recipes
This commit is contained in:
parent
9c5d127b4e
commit
5296ed10c1
@ -18,9 +18,9 @@ use sycamore::prelude::*;
|
||||
pub fn header() -> View<G> {
|
||||
view! {
|
||||
div(class="menu") {
|
||||
span { a(href="/ui/") { "home" }}
|
||||
span { a(href="/ui/") { "Home" }}
|
||||
" | "
|
||||
span { a(href="/ui/shopping/") { "shopping list" }}
|
||||
span { a(href="/ui/plan/") { "Meal Plan" }}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -48,8 +48,7 @@ fn steps(steps: ReadSignal<Vec<recipes::Step>>) -> View<G> {
|
||||
#[component(Recipe<G>)]
|
||||
pub fn recipe(idx: ReadSignal<usize>) -> View<G> {
|
||||
let app_service = use_context::<AppService>();
|
||||
// TODO(jwall): This does unnecessary copies. Can we eliminate that?
|
||||
let recipe = create_memo(move || app_service.get_recipes().get()[*idx.get()].1.clone());
|
||||
let recipe = app_service.get_recipes().get()[*idx.get()].1.clone();
|
||||
let title = create_memo(cloned!((recipe) => move || recipe.get().title.clone()));
|
||||
let desc = create_memo(
|
||||
cloned!((recipe) => move || recipe.clone().get().desc.clone().unwrap_or_else(|| String::new())),
|
||||
|
@ -14,6 +14,7 @@
|
||||
use crate::components::*;
|
||||
use crate::service::AppService;
|
||||
|
||||
use recipes;
|
||||
use sycamore::{context::use_context, prelude::*};
|
||||
|
||||
#[component(Start<G>)]
|
||||
@ -40,16 +41,15 @@ pub fn recipe_list() -> View<G> {
|
||||
let app_service = use_context::<AppService>();
|
||||
|
||||
let titles = create_memo(cloned!(app_service => move || {
|
||||
app_service.get_recipes().get().iter().map(|(i, r)| (*i, r.title.clone())).collect::<Vec<(usize, String)>>()
|
||||
app_service.get_recipes().get().iter().map(|(i, r)| (*i, r.clone())).collect::<Vec<(usize, Signal<recipes::Recipe>)>>()
|
||||
}));
|
||||
view! {
|
||||
ul(class="recipe_list") {
|
||||
Keyed(KeyedProps{
|
||||
Indexed(IndexedProps{
|
||||
iterable: titles,
|
||||
template: |(i, title)| {
|
||||
view! { li { a(href=format!("/ui/recipe/{}", i)) { (title) } } }
|
||||
template: |(i, recipe)| {
|
||||
view! { li { a(href=format!("/ui/recipe/{}", i)) { (recipe.get().title) } } }
|
||||
},
|
||||
key: |(i, title)| (*i, title.clone()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@
|
||||
// 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 crate::components::Recipe;
|
||||
use crate::console_log;
|
||||
use crate::service::AppService;
|
||||
use std::rc::Rc;
|
||||
@ -20,7 +21,7 @@ use sycamore::{context::use_context, prelude::*};
|
||||
|
||||
struct RecipeCheckBoxProps {
|
||||
i: usize,
|
||||
title: String,
|
||||
title: ReadSignal<String>,
|
||||
}
|
||||
|
||||
#[component(RecipeSelection<G>)]
|
||||
@ -38,7 +39,7 @@ fn recipe_selection(props: RecipeCheckBoxProps) -> View<G> {
|
||||
console_log!("setting recipe id: {} to count: {}", i, *count.get());
|
||||
app_service.set_recipe_count_by_index(i, count.get().parse().unwrap());
|
||||
})
|
||||
label(for=id_cloned_2) { (props.title) }
|
||||
label(for=id_cloned_2) { (props.title.get()) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -46,18 +47,17 @@ fn recipe_selection(props: RecipeCheckBoxProps) -> View<G> {
|
||||
pub fn recipe_selector() -> View<G> {
|
||||
let app_service = use_context::<AppService>();
|
||||
let titles = create_memo(cloned!(app_service => move || {
|
||||
app_service.get_recipes().get().iter().map(|(i, r)| (*i, r.title.clone())).collect::<Vec<(usize, String)>>()
|
||||
app_service.get_recipes().get().iter().map(|(i, r)| (*i, r.clone())).collect::<Vec<(usize, Signal<recipes::Recipe>)>>()
|
||||
}));
|
||||
view! {
|
||||
fieldset(class="recipe_selector") {
|
||||
Keyed(KeyedProps{
|
||||
Indexed(IndexedProps{
|
||||
iterable: titles,
|
||||
template: |(i, title)| {
|
||||
template: |(i, recipe)| {
|
||||
view! {
|
||||
RecipeSelection(RecipeCheckBoxProps{i: i, title: title})
|
||||
RecipeSelection(RecipeCheckBoxProps{i: i, title: create_memo(move || recipe.get().title.clone())})
|
||||
}
|
||||
},
|
||||
key: |(i, title)| (*i, title.clone()),
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -67,7 +67,7 @@ pub fn recipe_selector() -> View<G> {
|
||||
fn shopping_list() -> View<G> {
|
||||
let app_service = use_context::<AppService>();
|
||||
let ingredients = create_memo(move || {
|
||||
let ingredients = app_service.get_menu_list();
|
||||
let ingredients = app_service.get_shopping_list();
|
||||
ingredients
|
||||
.iter()
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
@ -76,6 +76,7 @@ fn shopping_list() -> View<G> {
|
||||
|
||||
// TODO(jwall): Sort by categories and names.
|
||||
view! {
|
||||
h1 { "Shopping List" }
|
||||
table(class="shopping_list") {
|
||||
tr {
|
||||
th { "Quantity" }
|
||||
@ -97,13 +98,33 @@ fn shopping_list() -> View<G> {
|
||||
}
|
||||
}
|
||||
|
||||
#[component(ShoppingView<G>)]
|
||||
pub fn shopping_view() -> View<G> {
|
||||
#[component(RecipeList<G>)]
|
||||
fn recipe_list() -> View<G> {
|
||||
let app_service = use_context::<AppService>();
|
||||
let menu_list = app_service.get_menu_list();
|
||||
view! {
|
||||
h1 { "Recipe List" }
|
||||
Indexed(IndexedProps{
|
||||
iterable: menu_list,
|
||||
template: |(idx, _count)| {
|
||||
let idx = Signal::new(idx);
|
||||
view ! {
|
||||
Recipe(idx.handle())
|
||||
hr()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[component(MealPlan<G>)]
|
||||
pub fn meal_plan() -> View<G> {
|
||||
view! {
|
||||
h1 {
|
||||
"Select your recipes"
|
||||
}
|
||||
RecipeSelector()
|
||||
ShoppingList()
|
||||
RecipeList()
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ use recipes::{parse, Ingredient, IngredientAccumulator, IngredientKey, Recipe};
|
||||
#[derive(Clone)]
|
||||
pub struct AppService {
|
||||
// TODO(jwall): Should each Recipe also be a Signal?
|
||||
recipes: Signal<Vec<(usize, Recipe)>>,
|
||||
recipes: Signal<Vec<(usize, Signal<Recipe>)>>,
|
||||
menu_list: Signal<BTreeMap<usize, usize>>,
|
||||
}
|
||||
|
||||
@ -64,16 +64,16 @@ impl AppService {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_recipe_by_index(&self, idx: usize) -> Option<Recipe> {
|
||||
pub fn get_recipe_by_index(&self, idx: usize) -> Option<Signal<Recipe>> {
|
||||
self.recipes.get().get(idx).map(|(_, r)| r.clone())
|
||||
}
|
||||
|
||||
pub fn get_menu_list(&self) -> BTreeMap<IngredientKey, Ingredient> {
|
||||
pub fn get_shopping_list(&self) -> BTreeMap<IngredientKey, Ingredient> {
|
||||
let mut acc = IngredientAccumulator::new();
|
||||
let recipe_counts = self.menu_list.get();
|
||||
for (idx, count) in recipe_counts.iter() {
|
||||
for _ in 0..*count {
|
||||
acc.accumulate_from(&self.get_recipe_by_index(*idx).unwrap());
|
||||
acc.accumulate_from(self.get_recipe_by_index(*idx).unwrap().get().as_ref());
|
||||
}
|
||||
}
|
||||
acc.ingredients()
|
||||
@ -89,11 +89,27 @@ impl AppService {
|
||||
self.menu_list.get().get(&i).map(|i| *i).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn get_recipes(&self) -> Signal<Vec<(usize, Recipe)>> {
|
||||
pub fn get_recipes(&self) -> Signal<Vec<(usize, Signal<Recipe>)>> {
|
||||
self.recipes.clone()
|
||||
}
|
||||
|
||||
pub fn set_recipes(&mut self, recipes: Vec<(usize, Recipe)>) {
|
||||
self.recipes.set(recipes);
|
||||
pub fn get_menu_list(&self) -> ReadSignal<Vec<(usize, usize)>> {
|
||||
let menu_list = self.menu_list.clone();
|
||||
create_memo(move || {
|
||||
menu_list
|
||||
.get()
|
||||
.iter()
|
||||
.map(|(idx, count)| (*idx, *count))
|
||||
.collect::<Vec<(usize, usize)>>()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_recipes(&mut self, mut recipes: Vec<(usize, Recipe)>) {
|
||||
self.recipes.set(
|
||||
recipes
|
||||
.drain(0..)
|
||||
.map(|(i, r)| (i, Signal::new(r)))
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -27,8 +27,8 @@ enum AppRoutes {
|
||||
Root,
|
||||
#[to("/ui/recipe/<index>")]
|
||||
Recipe { index: usize },
|
||||
#[to("/ui/shopping")]
|
||||
Menu,
|
||||
#[to("/ui/plan")]
|
||||
Plan,
|
||||
#[not_found]
|
||||
NotFound,
|
||||
}
|
||||
@ -42,8 +42,8 @@ fn route_switch<G: Html>(route: ReadSignal<AppRoutes>) -> View<G> {
|
||||
AppRoutes::Recipe { index: idx } => view! {
|
||||
RecipeView(*idx)
|
||||
},
|
||||
AppRoutes::Menu => view! {
|
||||
ShoppingView()
|
||||
AppRoutes::Plan => view! {
|
||||
MealPlan()
|
||||
},
|
||||
AppRoutes::NotFound => view! {
|
||||
"NotFound"
|
||||
|
Loading…
x
Reference in New Issue
Block a user