mirror of
https://github.com/zaphar/kitchen.git
synced 2025-07-22 19:40:14 -04:00
Use StateHandler in the shopping list
This commit is contained in:
parent
4b2563509b
commit
73c298661a
@ -51,6 +51,14 @@ pub fn as_categories(i: &str) -> std::result::Result<BTreeMap<String, String>, S
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn as_measure(i: &str) -> std::result::Result<Measure, String> {
|
||||||
|
match measure(StrIter::new(i)) {
|
||||||
|
Result::Abort(e) | Result::Fail(e) => Err(format_err(e)),
|
||||||
|
Result::Incomplete(_) => Err(format!("Incomplete categories list can not parse")),
|
||||||
|
Result::Complete(_, m) => Ok(m),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
make_fn!(
|
make_fn!(
|
||||||
pub categories<StrIter, BTreeMap<String, String>>,
|
pub categories<StrIter, BTreeMap<String, String>>,
|
||||||
do_each!(
|
do_each!(
|
||||||
|
@ -17,7 +17,7 @@ use base64;
|
|||||||
use reqwasm;
|
use reqwasm;
|
||||||
use serde_json::{from_str, to_string};
|
use serde_json::{from_str, to_string};
|
||||||
use sycamore::prelude::*;
|
use sycamore::prelude::*;
|
||||||
use tracing::{debug, error, info, instrument, warn};
|
use tracing::{debug, error, instrument, warn};
|
||||||
|
|
||||||
use client_api::*;
|
use client_api::*;
|
||||||
use recipes::{parse, IngredientKey, Recipe, RecipeEntry};
|
use recipes::{parse, IngredientKey, Recipe, RecipeEntry};
|
||||||
|
@ -13,34 +13,86 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
|
|
||||||
use recipes::{Ingredient, IngredientKey};
|
use recipes::{IngredientAccumulator, IngredientKey};
|
||||||
use sycamore::{futures::spawn_local_scoped, prelude::*};
|
use sycamore::prelude::*;
|
||||||
use tracing::{debug, info, instrument};
|
use tracing::{debug, info, instrument};
|
||||||
|
|
||||||
|
use crate::app_state::{Message, StateHandler};
|
||||||
|
|
||||||
fn make_ingredients_rows<'ctx, G: Html>(
|
fn make_ingredients_rows<'ctx, G: Html>(
|
||||||
cx: Scope<'ctx>,
|
cx: Scope<'ctx>,
|
||||||
ingredients: &'ctx ReadSignal<Vec<(IngredientKey, (Ingredient, BTreeSet<String>))>>,
|
sh: StateHandler<'ctx>,
|
||||||
modified_amts: RcSignal<BTreeMap<IngredientKey, RcSignal<String>>>,
|
show_staples: &'ctx ReadSignal<bool>,
|
||||||
filtered_keys: RcSignal<BTreeSet<IngredientKey>>,
|
|
||||||
) -> View<G> {
|
) -> View<G> {
|
||||||
|
let ingredients = sh.get_selector(cx, move |state| {
|
||||||
|
let state = state.get();
|
||||||
|
let mut acc = IngredientAccumulator::new();
|
||||||
|
for (id, count) in state.recipe_counts.iter() {
|
||||||
|
for _ in 0..(*count) {
|
||||||
|
acc.accumulate_from(
|
||||||
|
state
|
||||||
|
.recipes
|
||||||
|
.get(id)
|
||||||
|
.expect(&format!("No such recipe id exists: {}", id)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if *show_staples.get() {
|
||||||
|
if let Some(staples) = &state.staples {
|
||||||
|
acc.accumulate_from(staples);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
acc.ingredients()
|
||||||
|
.into_iter()
|
||||||
|
// First we filter out any filtered ingredients
|
||||||
|
.filter(|(i, _)| state.filtered_ingredients.contains(i))
|
||||||
|
// Then we take into account our modified amts
|
||||||
|
.map(|(k, (i, rs))| {
|
||||||
|
if state.modified_amts.contains_key(&k) {
|
||||||
|
(
|
||||||
|
k.clone(),
|
||||||
|
(
|
||||||
|
i.name,
|
||||||
|
i.form,
|
||||||
|
i.category,
|
||||||
|
state.modified_amts.get(&k).unwrap().clone(),
|
||||||
|
rs,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
k.clone(),
|
||||||
|
(
|
||||||
|
i.name,
|
||||||
|
i.form,
|
||||||
|
i.category,
|
||||||
|
format!("{}", i.amt.normalize()),
|
||||||
|
rs,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<(
|
||||||
|
IngredientKey,
|
||||||
|
(String, Option<String>, String, String, BTreeSet<String>),
|
||||||
|
)>>()
|
||||||
|
});
|
||||||
view!(
|
view!(
|
||||||
cx,
|
cx,
|
||||||
Indexed(
|
Indexed(
|
||||||
iterable = ingredients,
|
iterable = ingredients,
|
||||||
view = move |cx, (k, (i, rs))| {
|
view = move |cx, (k, (name, form, category, amt, rs))| {
|
||||||
let mut modified_amt_set = modified_amts.get().as_ref().clone();
|
let category = if category == "" {
|
||||||
let amt = modified_amt_set
|
|
||||||
.entry(k.clone())
|
|
||||||
.or_insert(create_rc_signal(format!("{}", i.amt.normalize())))
|
|
||||||
.clone();
|
|
||||||
modified_amts.set(modified_amt_set);
|
|
||||||
let name = i.name;
|
|
||||||
let category = if i.category == "" {
|
|
||||||
"other".to_owned()
|
"other".to_owned()
|
||||||
} else {
|
} else {
|
||||||
i.category
|
category
|
||||||
};
|
};
|
||||||
let form = i.form.map(|form| format!("({})", form)).unwrap_or_default();
|
let amt_signal = create_signal(cx, amt);
|
||||||
|
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 recipes = rs
|
let recipes = rs
|
||||||
.iter()
|
.iter()
|
||||||
.fold(String::new(), |acc, s| format!("{}{},", acc, s))
|
.fold(String::new(), |acc, s| format!("{}{},", acc, s))
|
||||||
@ -49,15 +101,12 @@ fn make_ingredients_rows<'ctx, G: Html>(
|
|||||||
view! {cx,
|
view! {cx,
|
||||||
tr {
|
tr {
|
||||||
td {
|
td {
|
||||||
input(bind:value=amt, type="text")
|
input(bind:value=amt_signal, type="text")
|
||||||
}
|
}
|
||||||
td {
|
td {
|
||||||
input(type="button", class="no-print destructive", value="X", on:click={
|
input(type="button", class="no-print destructive", value="X", on:click={
|
||||||
let filtered_keys = filtered_keys.clone();
|
|
||||||
move |_| {
|
move |_| {
|
||||||
let mut keyset = filtered_keys.get().as_ref().clone();
|
sh.dispatch(cx, Message::AddFilteredIngredient(k.clone()));
|
||||||
keyset.insert(k.clone());
|
|
||||||
filtered_keys.set(keyset);
|
|
||||||
}})
|
}})
|
||||||
}
|
}
|
||||||
td { (name) " " (form) "" br {} "" (category) "" }
|
td { (name) " " (form) "" br {} "" (category) "" }
|
||||||
@ -69,36 +118,44 @@ fn make_ingredients_rows<'ctx, G: Html>(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_extras_rows<'ctx, G: Html>(
|
fn make_extras_rows<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View<G> {
|
||||||
cx: Scope<'ctx>,
|
let extras_read_signal = sh.get_selector(cx, |state| {
|
||||||
extras: RcSignal<Vec<(usize, (RcSignal<String>, RcSignal<String>))>>,
|
state
|
||||||
) -> View<G> {
|
.get()
|
||||||
let extras_read_signal = create_memo(cx, {
|
.extras
|
||||||
let extras = extras.clone();
|
.iter()
|
||||||
move || extras.get().as_ref().clone()
|
.cloned()
|
||||||
|
.collect::<Vec<(String, String)>>()
|
||||||
});
|
});
|
||||||
view! {cx,
|
view! {cx,
|
||||||
Indexed(
|
Indexed(
|
||||||
iterable=extras_read_signal,
|
iterable=extras_read_signal,
|
||||||
view= move |cx, (idx, (amt, name))| {
|
view= move |cx, (amt, name)| {
|
||||||
|
let amt_signal = create_signal(cx, amt.clone());
|
||||||
|
let name_signal = create_signal(cx, name.clone());
|
||||||
|
create_effect(cx, {
|
||||||
|
let amt_clone = amt.clone();
|
||||||
|
let name_clone = name.clone();
|
||||||
|
move || {
|
||||||
|
let new_amt = amt_signal.get();
|
||||||
|
let new_name = name_signal.get();
|
||||||
|
sh.dispatch(cx, Message::RemoveExtra(amt_clone.clone(), name_clone.clone()));
|
||||||
|
sh.dispatch(cx, Message::AddExtra(new_amt.as_ref().clone(), new_name.as_ref().clone()));
|
||||||
|
}
|
||||||
|
});
|
||||||
view! {cx,
|
view! {cx,
|
||||||
tr {
|
tr {
|
||||||
td {
|
td {
|
||||||
input(bind:value=amt, type="text")
|
input(bind:value=amt_signal, type="text")
|
||||||
}
|
}
|
||||||
td {
|
td {
|
||||||
input(type="button", class="no-print destructive", value="X", on:click={
|
input(type="button", class="no-print destructive", value="X", on:click={
|
||||||
let extras = extras.clone();
|
|
||||||
move |_| {
|
move |_| {
|
||||||
extras.set(extras.get().iter()
|
sh.dispatch(cx, Message::RemoveExtra(amt.clone(), name.clone()));
|
||||||
.filter(|(i, _)| *i != idx)
|
|
||||||
.map(|(_, v)| v.clone())
|
|
||||||
.enumerate()
|
|
||||||
.collect())
|
|
||||||
}})
|
}})
|
||||||
}
|
}
|
||||||
td {
|
td {
|
||||||
input(bind:value=name, type="text")
|
input(bind:value=name_signal, type="text")
|
||||||
}
|
}
|
||||||
td { "Misc" }
|
td { "Misc" }
|
||||||
}
|
}
|
||||||
@ -110,14 +167,11 @@ fn make_extras_rows<'ctx, G: Html>(
|
|||||||
|
|
||||||
fn make_shopping_table<'ctx, G: Html>(
|
fn make_shopping_table<'ctx, G: Html>(
|
||||||
cx: Scope<'ctx>,
|
cx: Scope<'ctx>,
|
||||||
ingredients: &'ctx ReadSignal<Vec<(IngredientKey, (Ingredient, BTreeSet<String>))>>,
|
sh: StateHandler<'ctx>,
|
||||||
modified_amts: RcSignal<BTreeMap<IngredientKey, RcSignal<String>>>,
|
show_staples: &'ctx ReadSignal<bool>,
|
||||||
extras: RcSignal<Vec<(usize, (RcSignal<String>, RcSignal<String>))>>,
|
|
||||||
filtered_keys: RcSignal<BTreeSet<IngredientKey>>,
|
|
||||||
) -> View<G> {
|
) -> View<G> {
|
||||||
let extra_rows_view = make_extras_rows(cx, extras);
|
let extra_rows_view = make_extras_rows(cx, sh);
|
||||||
let ingredient_rows =
|
let ingredient_rows = make_ingredients_rows(cx, sh, show_staples);
|
||||||
make_ingredients_rows(cx, ingredients, modified_amts, filtered_keys.clone());
|
|
||||||
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 {
|
||||||
@ -134,12 +188,10 @@ fn make_shopping_table<'ctx, G: Html>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument]
|
#[instrument(skip_all)]
|
||||||
#[component]
|
#[component]
|
||||||
pub fn ShoppingList<G: Html>(cx: Scope) -> View<G> {
|
pub fn ShoppingList<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View<G> {
|
||||||
let state = crate::app_state::State::get_from_context(cx);
|
let filtered_keys = sh.get_selector(cx, |state| state.get().filtered_ingredients.clone());
|
||||||
// FIXME(jwall): We need to init this state for the page at some point.
|
|
||||||
let filtered_keys: RcSignal<BTreeSet<IngredientKey>> = state.filtered_ingredients.clone();
|
|
||||||
let ingredients_map = create_rc_signal(BTreeMap::new());
|
let ingredients_map = create_rc_signal(BTreeMap::new());
|
||||||
let show_staples = create_signal(cx, true);
|
let show_staples = create_signal(cx, true);
|
||||||
let save_click = create_signal(cx, ());
|
let save_click = create_signal(cx, ());
|
||||||
@ -169,17 +221,10 @@ pub fn ShoppingList<G: Html>(cx: Scope) -> View<G> {
|
|||||||
});
|
});
|
||||||
let table_view = create_signal(cx, View::empty());
|
let table_view = create_signal(cx, View::empty());
|
||||||
create_effect(cx, {
|
create_effect(cx, {
|
||||||
let filtered_keys = filtered_keys.clone();
|
|
||||||
let state = crate::app_state::State::get_from_context(cx);
|
let state = crate::app_state::State::get_from_context(cx);
|
||||||
move || {
|
move || {
|
||||||
if (ingredients.get().len() > 0) || (state.extras.get().len() > 0) {
|
if (ingredients.get().len() > 0) || (state.extras.get().len() > 0) {
|
||||||
table_view.set(make_shopping_table(
|
table_view.set(make_shopping_table(cx, sh, show_staples));
|
||||||
cx,
|
|
||||||
ingredients,
|
|
||||||
state.modified_amts.clone(),
|
|
||||||
state.extras.clone(),
|
|
||||||
filtered_keys.clone(),
|
|
||||||
));
|
|
||||||
} else {
|
} else {
|
||||||
table_view.set(View::empty());
|
table_view.set(View::empty());
|
||||||
}
|
}
|
||||||
@ -188,17 +233,7 @@ pub fn ShoppingList<G: Html>(cx: Scope) -> View<G> {
|
|||||||
create_effect(cx, move || {
|
create_effect(cx, move || {
|
||||||
save_click.track();
|
save_click.track();
|
||||||
info!("Registering save request for inventory");
|
info!("Registering save request for inventory");
|
||||||
spawn_local_scoped(cx, {
|
sh.dispatch(cx, Message::SaveState);
|
||||||
let state = crate::app_state::State::get_from_context(cx);
|
|
||||||
let store = crate::api::HttpStore::get_from_context(cx);
|
|
||||||
async move {
|
|
||||||
debug!(?state, "Attempting save for inventory");
|
|
||||||
store
|
|
||||||
.save_state(state)
|
|
||||||
.await
|
|
||||||
.expect("Unable to save inventory data");
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
let state = crate::app_state::State::get_from_context(cx);
|
let state = crate::app_state::State::get_from_context(cx);
|
||||||
view! {cx,
|
view! {cx,
|
||||||
@ -212,14 +247,15 @@ pub fn ShoppingList<G: Html>(cx: Scope) -> View<G> {
|
|||||||
state.extras.set(cloned_extras.drain(0..).enumerate().collect());
|
state.extras.set(cloned_extras.drain(0..).enumerate().collect());
|
||||||
})
|
})
|
||||||
input(type="button", value="Reset", class="no-print", on:click={
|
input(type="button", value="Reset", class="no-print", on:click={
|
||||||
let state = crate::app_state::State::get_from_context(cx);
|
//let state = crate::app_state::State::get_from_context(cx);
|
||||||
move |_| {
|
move |_| {
|
||||||
// TODO(jwall): We should actually pop up a modal here or use a different set of items.
|
// FIXME(jwall): This should be an event.
|
||||||
ingredients_map.set(state.get_shopping_list(*show_staples.get()));
|
// // TODO(jwall): We should actually pop up a modal here or use a different set of items.
|
||||||
// clear the filter_signal
|
// ingredients_map.set(state.get_shopping_list(*show_staples.get()));
|
||||||
filtered_keys.set(BTreeSet::new());
|
// // clear the filter_signal
|
||||||
state.modified_amts.set(BTreeMap::new());
|
// filtered_keys.set(BTreeSet::new());
|
||||||
state.extras.set(Vec::new());
|
// state.modified_amts.set(BTreeMap::new());
|
||||||
|
// state.extras.set(Vec::new());
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
input(type="button", value="Save", class="no-print", on:click=|_| {
|
input(type="button", value="Save", class="no-print", on:click=|_| {
|
||||||
|
@ -21,6 +21,6 @@ pub fn InventoryPage<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) ->
|
|||||||
view! {cx,
|
view! {cx,
|
||||||
PlanningPage(
|
PlanningPage(
|
||||||
selected=Some("Inventory".to_owned()),
|
selected=Some("Inventory".to_owned()),
|
||||||
) { ShoppingList() }
|
) { ShoppingList(sh) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user