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