mirror of
https://github.com/zaphar/kitchen.git
synced 2025-07-22 19:40:14 -04:00
Fix infinite signal loop in shopping list
This commit is contained in:
parent
0167e6070d
commit
77cae25c74
@ -20,6 +20,7 @@ use sycamore::futures::spawn_local_scoped;
|
|||||||
use sycamore::prelude::*;
|
use sycamore::prelude::*;
|
||||||
use sycamore_state::{Handler, MessageMapper};
|
use sycamore_state::{Handler, MessageMapper};
|
||||||
use tracing::{debug, error, info, instrument, warn};
|
use tracing::{debug, error, info, instrument, warn};
|
||||||
|
use wasm_bindgen::throw_str;
|
||||||
|
|
||||||
use crate::api::HttpStore;
|
use crate::api::HttpStore;
|
||||||
use crate::js_lib;
|
use crate::js_lib;
|
||||||
@ -27,7 +28,7 @@ use crate::js_lib;
|
|||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
pub recipe_counts: BTreeMap<String, usize>,
|
pub recipe_counts: BTreeMap<String, usize>,
|
||||||
pub extras: BTreeSet<(String, String)>,
|
pub extras: Vec<(String, String)>,
|
||||||
pub staples: Option<Recipe>,
|
pub staples: Option<Recipe>,
|
||||||
pub recipes: BTreeMap<String, Recipe>,
|
pub recipes: BTreeMap<String, Recipe>,
|
||||||
pub category_map: String,
|
pub category_map: String,
|
||||||
@ -40,7 +41,7 @@ impl AppState {
|
|||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
recipe_counts: BTreeMap::new(),
|
recipe_counts: BTreeMap::new(),
|
||||||
extras: BTreeSet::new(),
|
extras: Vec::new(),
|
||||||
staples: None,
|
staples: None,
|
||||||
recipes: BTreeMap::new(),
|
recipes: BTreeMap::new(),
|
||||||
category_map: String::new(),
|
category_map: String::new(),
|
||||||
@ -56,7 +57,8 @@ pub enum Message {
|
|||||||
ResetRecipeCounts,
|
ResetRecipeCounts,
|
||||||
UpdateRecipeCount(String, usize),
|
UpdateRecipeCount(String, usize),
|
||||||
AddExtra(String, String),
|
AddExtra(String, String),
|
||||||
RemoveExtra(String, String),
|
RemoveExtra(usize),
|
||||||
|
UpdateExtra(usize, String, String),
|
||||||
SaveRecipe(RecipeEntry),
|
SaveRecipe(RecipeEntry),
|
||||||
SetRecipe(String, Recipe),
|
SetRecipe(String, Recipe),
|
||||||
// TODO(jwall): Remove this annotation when safe to do so.
|
// TODO(jwall): Remove this annotation when safe to do so.
|
||||||
@ -171,7 +173,7 @@ impl StateMachine {
|
|||||||
Ok((filtered_ingredients, modified_amts, extra_items)) => {
|
Ok((filtered_ingredients, modified_amts, extra_items)) => {
|
||||||
state.modified_amts = modified_amts;
|
state.modified_amts = modified_amts;
|
||||||
state.filtered_ingredients = filtered_ingredients;
|
state.filtered_ingredients = filtered_ingredients;
|
||||||
state.extras = BTreeSet::from_iter(extra_items);
|
state.extras = extra_items;
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("{:?}", e);
|
error!("{:?}", e);
|
||||||
@ -198,11 +200,20 @@ impl MessageMapper<Message, AppState> for StateMachine {
|
|||||||
original_copy.recipe_counts.insert(id, count);
|
original_copy.recipe_counts.insert(id, count);
|
||||||
}
|
}
|
||||||
Message::AddExtra(amt, name) => {
|
Message::AddExtra(amt, name) => {
|
||||||
original_copy.extras.insert((amt, name));
|
original_copy.extras.push((amt, name));
|
||||||
}
|
}
|
||||||
Message::RemoveExtra(amt, name) => {
|
Message::RemoveExtra(idx) => {
|
||||||
original_copy.extras.remove(&(amt, name));
|
original_copy.extras.remove(idx);
|
||||||
}
|
}
|
||||||
|
Message::UpdateExtra(idx, amt, name) => match original_copy.extras.get_mut(idx) {
|
||||||
|
Some(extra) => {
|
||||||
|
extra.0 = amt;
|
||||||
|
extra.1 = name;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
throw_str("Attempted to remove extra that didn't exist");
|
||||||
|
}
|
||||||
|
},
|
||||||
Message::SetStaples(staples) => {
|
Message::SetStaples(staples) => {
|
||||||
original_copy.staples = staples;
|
original_copy.staples = staples;
|
||||||
}
|
}
|
||||||
@ -240,7 +251,7 @@ impl MessageMapper<Message, AppState> for StateMachine {
|
|||||||
Message::ResetInventory => {
|
Message::ResetInventory => {
|
||||||
original_copy.filtered_ingredients = BTreeSet::new();
|
original_copy.filtered_ingredients = BTreeSet::new();
|
||||||
original_copy.modified_amts = BTreeMap::new();
|
original_copy.modified_amts = BTreeMap::new();
|
||||||
original_copy.extras = BTreeSet::new();
|
original_copy.extras = Vec::new();
|
||||||
}
|
}
|
||||||
Message::AddFilteredIngredient(key) => {
|
Message::AddFilteredIngredient(key) => {
|
||||||
original_copy.filtered_ingredients.insert(key);
|
original_copy.filtered_ingredients.insert(key);
|
||||||
|
@ -15,17 +15,20 @@ use std::collections::BTreeSet;
|
|||||||
|
|
||||||
use recipes::{IngredientAccumulator, IngredientKey};
|
use recipes::{IngredientAccumulator, IngredientKey};
|
||||||
use sycamore::prelude::*;
|
use sycamore::prelude::*;
|
||||||
use tracing::{info, instrument};
|
use tracing::{debug, info, instrument};
|
||||||
|
|
||||||
use crate::app_state::{Message, StateHandler};
|
use crate::app_state::{Message, StateHandler};
|
||||||
|
|
||||||
|
#[instrument(skip_all)]
|
||||||
fn make_ingredients_rows<'ctx, G: Html>(
|
fn make_ingredients_rows<'ctx, G: Html>(
|
||||||
cx: Scope<'ctx>,
|
cx: Scope<'ctx>,
|
||||||
sh: StateHandler<'ctx>,
|
sh: StateHandler<'ctx>,
|
||||||
show_staples: &'ctx ReadSignal<bool>,
|
show_staples: &'ctx ReadSignal<bool>,
|
||||||
) -> View<G> {
|
) -> View<G> {
|
||||||
|
debug!("Making ingredients rows");
|
||||||
let ingredients = sh.get_selector(cx, move |state| {
|
let ingredients = sh.get_selector(cx, move |state| {
|
||||||
let state = state.get();
|
let state = state.get();
|
||||||
|
debug!("building ingredient list from state");
|
||||||
let mut acc = IngredientAccumulator::new();
|
let mut acc = IngredientAccumulator::new();
|
||||||
for (id, count) in state.recipe_counts.iter() {
|
for (id, count) in state.recipe_counts.iter() {
|
||||||
for _ in 0..(*count) {
|
for _ in 0..(*count) {
|
||||||
@ -45,7 +48,7 @@ fn make_ingredients_rows<'ctx, G: Html>(
|
|||||||
acc.ingredients()
|
acc.ingredients()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
// First we filter out any filtered ingredients
|
// First we filter out any filtered ingredients
|
||||||
.filter(|(i, _)| state.filtered_ingredients.contains(i))
|
.filter(|(i, _)| !state.filtered_ingredients.contains(i))
|
||||||
// Then we take into account our modified amts
|
// Then we take into account our modified amts
|
||||||
.map(|(k, (i, rs))| {
|
.map(|(k, (i, rs))| {
|
||||||
if state.modified_amts.contains_key(&k) {
|
if state.modified_amts.contains_key(&k) {
|
||||||
@ -89,9 +92,6 @@ fn make_ingredients_rows<'ctx, G: Html>(
|
|||||||
};
|
};
|
||||||
let amt_signal = create_signal(cx, amt);
|
let amt_signal = create_signal(cx, amt);
|
||||||
let k_clone = k.clone();
|
let k_clone = k.clone();
|
||||||
sh.bind_trigger(cx, &amt_signal, move |val| {
|
|
||||||
Message::UpdateAmt(k_clone.clone(), val.as_ref().clone())
|
|
||||||
});
|
|
||||||
let form = form.map(|form| format!("({})", form)).unwrap_or_default();
|
let form = form.map(|form| format!("({})", form)).unwrap_or_default();
|
||||||
let recipes = rs
|
let recipes = rs
|
||||||
.iter()
|
.iter()
|
||||||
@ -101,7 +101,9 @@ fn make_ingredients_rows<'ctx, G: Html>(
|
|||||||
view! {cx,
|
view! {cx,
|
||||||
tr {
|
tr {
|
||||||
td {
|
td {
|
||||||
input(bind:value=amt_signal, type="text")
|
input(bind:value=amt_signal, type="text", on:change=move |_| {
|
||||||
|
sh.dispatch(cx, Message::UpdateAmt(k_clone.clone(), amt_signal.get_untracked().as_ref().clone()));
|
||||||
|
})
|
||||||
}
|
}
|
||||||
td {
|
td {
|
||||||
input(type="button", class="no-print destructive", value="X", on:click={
|
input(type="button", class="no-print destructive", value="X", on:click={
|
||||||
@ -118,50 +120,44 @@ fn make_ingredients_rows<'ctx, G: Html>(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(skip_all)]
|
||||||
fn make_extras_rows<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View<G> {
|
fn make_extras_rows<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View<G> {
|
||||||
|
debug!("Making extras rows");
|
||||||
let extras_read_signal = sh.get_selector(cx, |state| {
|
let extras_read_signal = sh.get_selector(cx, |state| {
|
||||||
state
|
state.get().extras.iter().cloned().enumerate().collect()
|
||||||
.get()
|
|
||||||
.extras
|
|
||||||
.iter()
|
|
||||||
.cloned()
|
|
||||||
.collect::<Vec<(String, String)>>()
|
|
||||||
});
|
});
|
||||||
view! {cx,
|
view! {cx,
|
||||||
Indexed(
|
Indexed(
|
||||||
iterable=extras_read_signal,
|
iterable=extras_read_signal,
|
||||||
view= move |cx, (amt, name)| {
|
view= move |cx, (idx, (amt, name))| {
|
||||||
let amt_signal = create_signal(cx, amt.clone());
|
let amt_signal = create_signal(cx, amt.clone());
|
||||||
let name_signal = create_signal(cx, name.clone());
|
let name_signal = create_signal(cx, name.clone());
|
||||||
create_effect(cx, {
|
view! {cx,
|
||||||
let amt_clone = amt.clone();
|
tr {
|
||||||
let name_clone = name.clone();
|
td {
|
||||||
move || {
|
input(bind:value=amt_signal, type="text", on:change=move |_| {
|
||||||
let new_amt = amt_signal.get();
|
sh.dispatch(cx, Message::UpdateExtra(idx,
|
||||||
let new_name = name_signal.get();
|
amt_signal.get_untracked().as_ref().clone(),
|
||||||
sh.dispatch(cx, Message::RemoveExtra(amt_clone.clone(), name_clone.clone()));
|
name_signal.get_untracked().as_ref().clone()));
|
||||||
sh.dispatch(cx, Message::AddExtra(new_amt.as_ref().clone(), new_name.as_ref().clone()));
|
})
|
||||||
}
|
|
||||||
});
|
|
||||||
view! {cx,
|
|
||||||
tr {
|
|
||||||
td {
|
|
||||||
input(bind:value=amt_signal, type="text")
|
|
||||||
}
|
|
||||||
td {
|
|
||||||
input(type="button", class="no-print destructive", value="X", on:click={
|
|
||||||
move |_| {
|
|
||||||
sh.dispatch(cx, Message::RemoveExtra(amt.clone(), name.clone()));
|
|
||||||
}})
|
|
||||||
}
|
|
||||||
td {
|
|
||||||
input(bind:value=name_signal, type="text")
|
|
||||||
}
|
|
||||||
td { "Misc" }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
td {
|
||||||
|
input(type="button", class="no-print destructive", value="X", on:click=move |_| {
|
||||||
|
sh.dispatch(cx, Message::RemoveExtra(idx));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
input(bind:value=name_signal, type="text", on:change=move |_| {
|
||||||
|
sh.dispatch(cx, Message::UpdateExtra(idx,
|
||||||
|
amt_signal.get_untracked().as_ref().clone(),
|
||||||
|
name_signal.get_untracked().as_ref().clone()));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
td { "Misc" }
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,8 +166,7 @@ fn make_shopping_table<'ctx, G: Html>(
|
|||||||
sh: StateHandler<'ctx>,
|
sh: StateHandler<'ctx>,
|
||||||
show_staples: &'ctx ReadSignal<bool>,
|
show_staples: &'ctx ReadSignal<bool>,
|
||||||
) -> View<G> {
|
) -> View<G> {
|
||||||
let extra_rows_view = make_extras_rows(cx, sh);
|
debug!("Making shopping table");
|
||||||
let ingredient_rows = make_ingredients_rows(cx, sh, show_staples);
|
|
||||||
view! {cx,
|
view! {cx,
|
||||||
table(class="pad-top shopping-list page-breaker container-fluid", role="grid") {
|
table(class="pad-top shopping-list page-breaker container-fluid", role="grid") {
|
||||||
tr {
|
tr {
|
||||||
@ -181,8 +176,8 @@ fn make_shopping_table<'ctx, G: Html>(
|
|||||||
th { " Recipes " }
|
th { " Recipes " }
|
||||||
}
|
}
|
||||||
tbody {
|
tbody {
|
||||||
(ingredient_rows)
|
(make_ingredients_rows(cx, sh, show_staples))
|
||||||
(extra_rows_view)
|
(make_extras_rows(cx, sh))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -198,13 +193,15 @@ pub fn ShoppingList<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> V
|
|||||||
input(id="show_staples_cb", type="checkbox", bind:checked=show_staples)
|
input(id="show_staples_cb", type="checkbox", bind:checked=show_staples)
|
||||||
(make_shopping_table(cx, sh, show_staples))
|
(make_shopping_table(cx, sh, show_staples))
|
||||||
input(type="button", value="Add Item", class="no-print", on:click=move |_| {
|
input(type="button", value="Add Item", class="no-print", on:click=move |_| {
|
||||||
|
info!("Registering add item request for inventory");
|
||||||
sh.dispatch(cx, Message::AddExtra(String::new(), String::new()));
|
sh.dispatch(cx, Message::AddExtra(String::new(), String::new()));
|
||||||
})
|
})
|
||||||
input(type="button", value="Reset", class="no-print", on:click=move |_| {
|
input(type="button", value="Reset", class="no-print", on:click=move |_| {
|
||||||
sh.dispatch(cx, Message::ResetInventory);
|
info!("Registering reset request for inventory");
|
||||||
|
sh.dispatch(cx, Message::ResetInventory);
|
||||||
})
|
})
|
||||||
input(type="button", value="Save", class="no-print", on:click=move |_| {
|
input(type="button", value="Save", class="no-print", on:click=move |_| {
|
||||||
info!("Registering save request for inventory");
|
info!("Registering save request for inventory");
|
||||||
sh.dispatch(cx, Message::SaveState);
|
sh.dispatch(cx, Message::SaveState);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user