mirror of
https://github.com/zaphar/kitchen.git
synced 2025-07-22 19:40:14 -04:00
parent
e40b84173e
commit
fbd4aeb59c
@ -86,7 +86,7 @@ pub fn output_ingredients_list(rs: Vec<Recipe>) {
|
|||||||
for r in rs {
|
for r in rs {
|
||||||
acc.accumulate_from(&r);
|
acc.accumulate_from(&r);
|
||||||
}
|
}
|
||||||
for (_, i) in acc.ingredients() {
|
for (_, (i, _)) in acc.ingredients() {
|
||||||
print!("{}", i.amt.normalize());
|
print!("{}", i.amt.normalize());
|
||||||
println!(" {}", i.name);
|
println!(" {}", i.name);
|
||||||
}
|
}
|
||||||
@ -99,7 +99,7 @@ pub fn output_ingredients_csv(rs: Vec<Recipe>) {
|
|||||||
}
|
}
|
||||||
let out = std::io::stdout();
|
let out = std::io::stdout();
|
||||||
let mut writer = csv::Writer::from_writer(out);
|
let mut writer = csv::Writer::from_writer(out);
|
||||||
for (_, i) in acc.ingredients() {
|
for (_, (i, _)) in acc.ingredients() {
|
||||||
writer
|
writer
|
||||||
.write_record(&[format!("{}", i.amt.normalize()), i.name])
|
.write_record(&[format!("{}", i.amt.normalize()), i.name])
|
||||||
.expect("Failed to write csv.");
|
.expect("Failed to write csv.");
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
pub mod parse;
|
pub mod parse;
|
||||||
pub mod unit;
|
pub mod unit;
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
|
|
||||||
use chrono::NaiveDate;
|
use chrono::NaiveDate;
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ impl Mealplan {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A Recipe with a title, description, and a series of steps.
|
/// A Recipe with a title, description, and a series of steps.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Ord, Eq)]
|
||||||
pub struct Recipe {
|
pub struct Recipe {
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub desc: Option<String>,
|
pub desc: Option<String>,
|
||||||
@ -92,11 +92,14 @@ impl Recipe {
|
|||||||
let mut acc = IngredientAccumulator::new();
|
let mut acc = IngredientAccumulator::new();
|
||||||
acc.accumulate_from(&self);
|
acc.accumulate_from(&self);
|
||||||
acc.ingredients()
|
acc.ingredients()
|
||||||
|
.into_iter()
|
||||||
|
.map(|(k, v)| (k, v.0))
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct IngredientAccumulator {
|
pub struct IngredientAccumulator {
|
||||||
inner: BTreeMap<IngredientKey, Ingredient>,
|
inner: BTreeMap<IngredientKey, (Ingredient, BTreeSet<String>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IngredientAccumulator {
|
impl IngredientAccumulator {
|
||||||
@ -110,26 +113,31 @@ impl IngredientAccumulator {
|
|||||||
for i in r.steps.iter().map(|s| s.ingredients.iter()).flatten() {
|
for i in r.steps.iter().map(|s| s.ingredients.iter()).flatten() {
|
||||||
let key = i.key();
|
let key = i.key();
|
||||||
if !self.inner.contains_key(&key) {
|
if !self.inner.contains_key(&key) {
|
||||||
self.inner.insert(key, i.clone());
|
let mut set = BTreeSet::new();
|
||||||
|
set.insert(r.title.clone());
|
||||||
|
self.inner.insert(key, (i.clone(), set));
|
||||||
} else {
|
} else {
|
||||||
let amt = match (self.inner[&key].amt, i.amt) {
|
let amt = match (self.inner[&key].0.amt, i.amt) {
|
||||||
(Volume(rvm), Volume(lvm)) => Volume(lvm + rvm),
|
(Volume(rvm), Volume(lvm)) => Volume(lvm + rvm),
|
||||||
(Count(lqty), Count(rqty)) => Count(lqty + rqty),
|
(Count(lqty), Count(rqty)) => Count(lqty + rqty),
|
||||||
(Weight(lqty), Weight(rqty)) => Weight(lqty + rqty),
|
(Weight(lqty), Weight(rqty)) => Weight(lqty + rqty),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
self.inner.get_mut(&key).map(|i| i.amt = amt);
|
self.inner.get_mut(&key).map(|(i, set)| {
|
||||||
|
i.amt = amt;
|
||||||
|
set.insert(r.title.clone());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ingredients(self) -> BTreeMap<IngredientKey, Ingredient> {
|
pub fn ingredients(self) -> BTreeMap<IngredientKey, (Ingredient, BTreeSet<String>)> {
|
||||||
self.inner
|
self.inner
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// A Recipe step. It has the time for the step if there is one, instructions, and an ingredients
|
/// A Recipe step. It has the time for the step if there is one, instructions, and an ingredients
|
||||||
/// list.
|
/// list.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Ord, Eq)]
|
||||||
pub struct Step {
|
pub struct Step {
|
||||||
pub prep_time: Option<std::time::Duration>,
|
pub prep_time: Option<std::time::Duration>,
|
||||||
pub instructions: String,
|
pub instructions: String,
|
||||||
@ -172,7 +180,7 @@ 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
|
||||||
/// uniquely identify an ingredient.
|
/// uniquely identify an ingredient.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord)]
|
||||||
pub struct Ingredient {
|
pub struct Ingredient {
|
||||||
pub id: Option<i64>, // TODO(jwall): use uuid instead?
|
pub id: Option<i64>, // TODO(jwall): use uuid instead?
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
@ -26,7 +26,7 @@ use std::{
|
|||||||
|
|
||||||
use num_rational::Ratio;
|
use num_rational::Ratio;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug, PartialOrd, Eq, Ord)]
|
||||||
/// Volume Measurements for ingredients in a recipe.
|
/// Volume Measurements for ingredients in a recipe.
|
||||||
pub enum VolumeMeasure {
|
pub enum VolumeMeasure {
|
||||||
// Imperial volume measurements. US.
|
// Imperial volume measurements. US.
|
||||||
@ -225,7 +225,7 @@ impl Display for VolumeMeasure {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug, PartialOrd, Eq, Ord)]
|
||||||
pub enum WeightMeasure {
|
pub enum WeightMeasure {
|
||||||
Gram(Quantity),
|
Gram(Quantity),
|
||||||
Kilogram(Quantity),
|
Kilogram(Quantity),
|
||||||
@ -338,7 +338,7 @@ impl Display for WeightMeasure {
|
|||||||
|
|
||||||
use WeightMeasure::{Gram, Kilogram, Oz, Pound};
|
use WeightMeasure::{Gram, Kilogram, Oz, Pound};
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord)]
|
||||||
/// Measurements in a Recipe with associated units for them.
|
/// Measurements in a Recipe with associated units for them.
|
||||||
pub enum Measure {
|
pub enum Measure {
|
||||||
/// Volume measurements as meter cubed base unit
|
/// Volume measurements as meter cubed base unit
|
||||||
@ -447,7 +447,7 @@ impl Display for Measure {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a Quantity for an ingredient of a recipe.
|
/// Represents a Quantity for an ingredient of a recipe.
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug, Eq, Ord)]
|
||||||
pub enum Quantity {
|
pub enum Quantity {
|
||||||
/// Whole or non fractional quantities of an ingredient in a recipe.
|
/// Whole or non fractional quantities of an ingredient in a recipe.
|
||||||
Whole(u32),
|
Whole(u32),
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
use crate::service::AppService;
|
use crate::service::AppService;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::collections::{BTreeMap, HashSet};
|
use std::collections::{BTreeMap, BTreeSet, HashSet};
|
||||||
|
|
||||||
use recipes::{Ingredient, IngredientKey};
|
use recipes::{Ingredient, IngredientKey};
|
||||||
use sycamore::{context::use_context, prelude::*};
|
use sycamore::{context::use_context, prelude::*};
|
||||||
@ -33,7 +33,7 @@ pub fn shopping_list() -> View<G> {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|(k, v)| (k.clone(), v.clone()))
|
.map(|(k, v)| (k.clone(), v.clone()))
|
||||||
.filter(|(k, _v)| !filtered_keys.get().contains(k))
|
.filter(|(k, _v)| !filtered_keys.get().contains(k))
|
||||||
.collect::<Vec<(IngredientKey, Ingredient)>>()
|
.collect::<Vec<(IngredientKey, (Ingredient, BTreeSet<String>))>>()
|
||||||
}));
|
}));
|
||||||
let table_view = Signal::new(View::empty());
|
let table_view = Signal::new(View::empty());
|
||||||
create_effect(
|
create_effect(
|
||||||
@ -44,15 +44,17 @@ pub fn shopping_list() -> View<G> {
|
|||||||
tr {
|
tr {
|
||||||
th { " Quantity " }
|
th { " Quantity " }
|
||||||
th { " Ingredient " }
|
th { " Ingredient " }
|
||||||
|
th { " Recipes " }
|
||||||
}
|
}
|
||||||
tbody {Indexed(IndexedProps{
|
tbody {Indexed(IndexedProps{
|
||||||
iterable: ingredients.clone(),
|
iterable: ingredients.clone(),
|
||||||
template: cloned!((filtered_keys, modified_amts) => move |(k, i)| {
|
template: cloned!((filtered_keys, modified_amts) => move |(k, (i, rs))| {
|
||||||
let mut modified_amt_set = (*modified_amts.get()).clone();
|
let mut modified_amt_set = (*modified_amts.get()).clone();
|
||||||
let amt = modified_amt_set.entry(k.clone()).or_insert(Signal::new(format!("{}", i.amt.normalize()))).clone();
|
let amt = modified_amt_set.entry(k.clone()).or_insert(Signal::new(format!("{}", i.amt.normalize()))).clone();
|
||||||
modified_amts.set(modified_amt_set);
|
modified_amts.set(modified_amt_set);
|
||||||
let name = i.name;
|
let name = i.name;
|
||||||
let form = i.form.map(|form| format!("({})", form)).unwrap_or_default();
|
let form = i.form.map(|form| format!("({})", form)).unwrap_or_default();
|
||||||
|
let names = rs.iter().fold(String::new(), |acc, s| format!("{}{},", acc, s)).trim_end_matches(",").to_owned();
|
||||||
view! {
|
view! {
|
||||||
tr {
|
tr {
|
||||||
td { input(bind:value=amt.clone(), class="ingredient-count-sel", type="text") }
|
td { input(bind:value=amt.clone(), class="ingredient-count-sel", type="text") }
|
||||||
@ -60,7 +62,8 @@ pub fn shopping_list() -> View<G> {
|
|||||||
let mut keyset = (*filtered_keys.get()).clone();
|
let mut keyset = (*filtered_keys.get()).clone();
|
||||||
keyset.insert(k.clone());
|
keyset.insert(k.clone());
|
||||||
filtered_keys.set(keyset);
|
filtered_keys.set(keyset);
|
||||||
})) " " (name) " " (form) }
|
})) " " (name) " " (form) "" }
|
||||||
|
td { (names) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
@ -11,7 +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 std::collections::BTreeMap;
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
|
|
||||||
use crate::{console_debug, console_error, console_log};
|
use crate::{console_debug, console_error, console_log};
|
||||||
|
|
||||||
@ -124,7 +124,7 @@ impl AppService {
|
|||||||
self.recipes.get().get(idx).map(|(_, r)| r.clone())
|
self.recipes.get().get(idx).map(|(_, r)| r.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_shopping_list(&self) -> BTreeMap<IngredientKey, Ingredient> {
|
pub fn get_shopping_list(&self) -> BTreeMap<IngredientKey, (Ingredient, BTreeSet<String>)> {
|
||||||
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() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user