mirror of
https://github.com/zaphar/kitchen.git
synced 2025-07-21 19:29:49 -04:00
Store app state atomically as one json blob
This commit is contained in:
parent
806fdd2721
commit
b496cf9568
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -1429,6 +1429,7 @@ dependencies = [
|
||||
"js-sys",
|
||||
"recipes",
|
||||
"reqwasm",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sycamore",
|
||||
"sycamore-router",
|
||||
|
@ -26,6 +26,10 @@ base64 = "0.21.0"
|
||||
sycamore-router = "0.8"
|
||||
js-sys = "0.3.60"
|
||||
|
||||
[dependencies.serde]
|
||||
version = "1.0"
|
||||
features = ["derive"]
|
||||
|
||||
[dependencies.tracing-subscriber]
|
||||
version = "0.3.16"
|
||||
features = ["fmt", "time"]
|
||||
|
214
web/src/api.rs
214
web/src/api.rs
@ -76,10 +76,6 @@ fn recipe_key<S: std::fmt::Display>(id: S) -> String {
|
||||
format!("recipe:{}", id)
|
||||
}
|
||||
|
||||
fn category_key<S: std::fmt::Display>(id: S) -> String {
|
||||
format!("category:{}", id)
|
||||
}
|
||||
|
||||
fn token68(user: String, pass: String) -> String {
|
||||
base64::engine::general_purpose::STANDARD.encode(format!("{}:{}", user, pass))
|
||||
}
|
||||
@ -96,6 +92,19 @@ impl LocalStore {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn store_app_state(&self, state: &AppState) {
|
||||
self.migrate_local_store();
|
||||
self.store
|
||||
.set("app_state", &to_string(state).unwrap())
|
||||
.expect("Failed to set our app state");
|
||||
}
|
||||
|
||||
pub fn fetch_app_state(&self) -> Option<AppState> {
|
||||
self.store.get("app_state").map_or(None, |val| {
|
||||
val.map(|s| from_str(&s).expect("Failed to deserialize app state"))
|
||||
})
|
||||
}
|
||||
|
||||
/// Gets user data from local storage.
|
||||
pub fn get_user_data(&self) -> Option<UserData> {
|
||||
self.store
|
||||
@ -120,43 +129,6 @@ impl LocalStore {
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets categories from local storage.
|
||||
pub fn get_categories(&self) -> Option<Vec<(String, String)>> {
|
||||
let mut mappings = Vec::new();
|
||||
for k in self.get_category_keys() {
|
||||
if let Some(mut cat_map) = self
|
||||
.store
|
||||
.get(&k)
|
||||
.expect(&format!("Failed to get category key {}", k))
|
||||
.map(|v| {
|
||||
from_str::<Vec<(String, String)>>(&v)
|
||||
.expect(&format!("Failed to parse category key {}", k))
|
||||
})
|
||||
{
|
||||
mappings.extend(cat_map.drain(0..));
|
||||
}
|
||||
}
|
||||
if mappings.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(mappings)
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the categories to the given string.
|
||||
pub fn set_categories(&self, mappings: Option<&Vec<(String, String)>>) {
|
||||
if let Some(mappings) = mappings {
|
||||
for (i, cat) in mappings.iter() {
|
||||
self.store
|
||||
.set(
|
||||
&category_key(i),
|
||||
&to_string(&(i, cat)).expect("Failed to serialize category mapping"),
|
||||
)
|
||||
.expect("Failed to store category mapping");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_storage_keys(&self) -> Vec<String> {
|
||||
let mut keys = Vec::new();
|
||||
for idx in 0..self.store.length().unwrap() {
|
||||
@ -167,10 +139,14 @@ impl LocalStore {
|
||||
keys
|
||||
}
|
||||
|
||||
fn get_category_keys(&self) -> impl Iterator<Item = String> {
|
||||
self.get_storage_keys()
|
||||
fn migrate_local_store(&self) {
|
||||
for k in self.get_storage_keys()
|
||||
.into_iter()
|
||||
.filter(|k| k.starts_with("category:"))
|
||||
.filter(|k| k.starts_with("categor") || k == "inventory" || k.starts_with("plan") || k == "staples") {
|
||||
// Deleting old local store key
|
||||
debug!("Deleting old local store key {}", k);
|
||||
self.store.delete(&k).expect("Failed to delete storage key");
|
||||
}
|
||||
}
|
||||
|
||||
fn get_recipe_keys(&self) -> impl Iterator<Item = String> {
|
||||
@ -241,110 +217,6 @@ impl LocalStore {
|
||||
.delete(&recipe_key(recipe_id))
|
||||
.expect(&format!("Failed to delete recipe {}", recipe_id))
|
||||
}
|
||||
|
||||
/// Save working plan to local storage.
|
||||
pub fn store_plan(&self, plan: &Vec<(String, i32)>) {
|
||||
self.store
|
||||
.set("plan", &to_string(&plan).expect("Failed to serialize plan"))
|
||||
.expect("Failed to store plan'");
|
||||
}
|
||||
|
||||
pub fn get_plan(&self) -> Option<Vec<(String, i32)>> {
|
||||
if let Some(plan) = self.store.get("plan").expect("Failed to store plan") {
|
||||
Some(from_str(&plan).expect("Failed to deserialize plan"))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete_plan(&self) {
|
||||
self.store.delete("plan").expect("Failed to delete plan");
|
||||
self.store
|
||||
.delete("inventory")
|
||||
.expect("Failed to delete inventory data");
|
||||
}
|
||||
|
||||
pub fn set_plan_date(&self, date: &NaiveDate) {
|
||||
self.store
|
||||
.set(
|
||||
"plan:date",
|
||||
&to_string(&date).expect("Failed to serialize plan:date"),
|
||||
)
|
||||
.expect("Failed to store plan:date");
|
||||
}
|
||||
|
||||
pub fn get_plan_date(&self) -> Option<NaiveDate> {
|
||||
if let Some(date) = self
|
||||
.store
|
||||
.get("plan:date")
|
||||
.expect("Failed to get plan date")
|
||||
{
|
||||
Some(from_str(&date).expect("Failed to deserialize plan_date"))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_inventory_data(
|
||||
&self,
|
||||
) -> Option<(
|
||||
BTreeSet<IngredientKey>,
|
||||
BTreeMap<IngredientKey, String>,
|
||||
Vec<(String, String)>,
|
||||
)> {
|
||||
if let Some(inventory) = self
|
||||
.store
|
||||
.get("inventory")
|
||||
.expect("Failed to retrieve inventory data")
|
||||
{
|
||||
let (filtered, modified, extras): (
|
||||
BTreeSet<IngredientKey>,
|
||||
Vec<(IngredientKey, String)>,
|
||||
Vec<(String, String)>,
|
||||
) = from_str(&inventory).expect("Failed to deserialize inventory");
|
||||
return Some((filtered, BTreeMap::from_iter(modified), extras));
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
pub fn set_inventory_data(
|
||||
&self,
|
||||
inventory: (
|
||||
&BTreeSet<IngredientKey>,
|
||||
&BTreeMap<IngredientKey, String>,
|
||||
&Vec<(String, String)>,
|
||||
),
|
||||
) {
|
||||
let filtered = inventory.0;
|
||||
let modified_amts = inventory
|
||||
.1
|
||||
.iter()
|
||||
.map(|(k, amt)| (k.clone(), amt.clone()))
|
||||
.collect::<Vec<(IngredientKey, String)>>();
|
||||
let extras = inventory.2;
|
||||
let inventory_data = (filtered, &modified_amts, extras);
|
||||
self.store
|
||||
.set(
|
||||
"inventory",
|
||||
&to_string(&inventory_data).expect(&format!(
|
||||
"Failed to serialize inventory {:?}",
|
||||
inventory_data
|
||||
)),
|
||||
)
|
||||
.expect("Failed to set inventory");
|
||||
}
|
||||
|
||||
pub fn set_staples(&self, content: &String) {
|
||||
self.store
|
||||
.set("staples", content)
|
||||
.expect("Failed to set staples in local store");
|
||||
}
|
||||
|
||||
pub fn get_staples(&self) -> Option<String> {
|
||||
self.store
|
||||
.get("staples")
|
||||
.expect("Failed to retreive staples from local store")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@ -434,7 +306,7 @@ impl HttpStore {
|
||||
Ok(resp) => resp,
|
||||
Err(reqwasm::Error::JsError(err)) => {
|
||||
error!(path, ?err, "Error hitting api");
|
||||
return Ok(self.local_store.get_categories());
|
||||
return Ok(None);
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(err)?;
|
||||
@ -706,22 +578,22 @@ impl HttpStore {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn fetch_plan(&self) -> Result<Option<Vec<(String, i32)>>, Error> {
|
||||
let mut path = self.v2_path();
|
||||
path.push_str("/plan");
|
||||
let resp = reqwasm::http::Request::get(&path).send().await?;
|
||||
if resp.status() != 200 {
|
||||
Err(format!("Status: {}", resp.status()).into())
|
||||
} else {
|
||||
debug!("We got a valid response back");
|
||||
let plan = resp
|
||||
.json::<PlanDataResponse>()
|
||||
.await
|
||||
.map_err(|e| format!("{}", e))?
|
||||
.as_success();
|
||||
Ok(plan)
|
||||
}
|
||||
}
|
||||
//pub async fn fetch_plan(&self) -> Result<Option<Vec<(String, i32)>>, Error> {
|
||||
// let mut path = self.v2_path();
|
||||
// path.push_str("/plan");
|
||||
// let resp = reqwasm::http::Request::get(&path).send().await?;
|
||||
// if resp.status() != 200 {
|
||||
// Err(format!("Status: {}", resp.status()).into())
|
||||
// } else {
|
||||
// debug!("We got a valid response back");
|
||||
// let plan = resp
|
||||
// .json::<PlanDataResponse>()
|
||||
// .await
|
||||
// .map_err(|e| format!("{}", e))?
|
||||
// .as_success();
|
||||
// Ok(plan)
|
||||
// }
|
||||
//}
|
||||
|
||||
pub async fn fetch_inventory_for_date(
|
||||
&self,
|
||||
@ -740,11 +612,7 @@ impl HttpStore {
|
||||
path.push_str(&format!("/{}", date));
|
||||
let resp = reqwasm::http::Request::get(&path).send().await?;
|
||||
if resp.status() != 200 {
|
||||
let err = Err(format!("Status: {}", resp.status()).into());
|
||||
Ok(match self.local_store.get_inventory_data() {
|
||||
Some(val) => val,
|
||||
None => return err,
|
||||
})
|
||||
Err(format!("Status: {}", resp.status()).into())
|
||||
} else {
|
||||
debug!("We got a valid response back");
|
||||
let InventoryData {
|
||||
@ -779,11 +647,7 @@ impl HttpStore {
|
||||
path.push_str("/inventory");
|
||||
let resp = reqwasm::http::Request::get(&path).send().await?;
|
||||
if resp.status() != 200 {
|
||||
let err = Err(format!("Status: {}", resp.status()).into());
|
||||
Ok(match self.local_store.get_inventory_data() {
|
||||
Some(val) => val,
|
||||
None => return err,
|
||||
})
|
||||
Err(format!("Status: {}", resp.status()).into())
|
||||
} else {
|
||||
debug!("We got a valid response back");
|
||||
let InventoryData {
|
||||
|
@ -19,6 +19,7 @@ use std::{
|
||||
use chrono::NaiveDate;
|
||||
use client_api::UserData;
|
||||
use recipes::{parse, Ingredient, IngredientKey, Recipe, RecipeEntry};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sycamore::futures::spawn_local_scoped;
|
||||
use sycamore::prelude::*;
|
||||
use sycamore_state::{Handler, MessageMapper};
|
||||
@ -30,12 +31,14 @@ use crate::{
|
||||
components,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct AppState {
|
||||
pub recipe_counts: BTreeMap<String, usize>,
|
||||
pub recipe_categories: BTreeMap<String, String>,
|
||||
pub extras: Vec<(String, String)>,
|
||||
#[serde(skip)] // FIXME(jwall): This should really be storable I think?
|
||||
pub staples: Option<BTreeSet<Ingredient>>,
|
||||
#[serde(skip)] // FIXME(jwall): This should really be storable I think?
|
||||
pub recipes: BTreeMap<String, Recipe>,
|
||||
pub category_map: BTreeMap<String, String>,
|
||||
pub filtered_ingredients: BTreeSet<IngredientKey>,
|
||||
@ -162,35 +165,35 @@ impl StateMachine {
|
||||
local_store: &LocalStore,
|
||||
original: &Signal<AppState>,
|
||||
) -> Result<(), crate::api::Error> {
|
||||
// TODO(jwall): Load plan state from local_store first.
|
||||
if let Some(app_state) = local_store.fetch_app_state() {
|
||||
original.set_silent(app_state);
|
||||
}
|
||||
let mut state = original.get().as_ref().clone();
|
||||
info!("Synchronizing Recipes");
|
||||
let recipe_entries = &store.fetch_recipes().await?;
|
||||
let recipes = parse_recipes(&recipe_entries)?;
|
||||
|
||||
debug!(?recipes, "Parsed Recipes");
|
||||
if let Some(recipes) = recipes {
|
||||
state.recipes = recipes;
|
||||
};
|
||||
|
||||
info!("Synchronizing staples");
|
||||
state.staples = if let Some(content) = store.fetch_staples().await? {
|
||||
local_store.set_staples(&content);
|
||||
// now we need to parse staples as ingredients
|
||||
let mut staples = parse::as_ingredient_list(&content)?;
|
||||
Some(staples.drain(0..).collect())
|
||||
} else {
|
||||
if let Some(content) = local_store.get_staples() {
|
||||
let mut staples = parse::as_ingredient_list(&content)?;
|
||||
Some(staples.drain(0..).collect())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
Some(BTreeSet::new())
|
||||
};
|
||||
|
||||
info!("Synchronizing recipe");
|
||||
if let Some(recipe_entries) = recipe_entries {
|
||||
local_store.set_all_recipes(recipe_entries);
|
||||
state.recipe_categories = recipe_entries
|
||||
.iter()
|
||||
.map(|entry| {
|
||||
debug!(recipe_entry=?entry, "Getting recipe category");
|
||||
(
|
||||
entry.recipe_id().to_owned(),
|
||||
entry
|
||||
@ -203,19 +206,19 @@ impl StateMachine {
|
||||
}
|
||||
|
||||
info!("Fetching meal plan list");
|
||||
let plan_dates = store.fetch_plan_dates().await?;
|
||||
if let Some(mut plan_dates) = plan_dates {
|
||||
if let Some(mut plan_dates) = store.fetch_plan_dates().await? {
|
||||
debug!(?plan_dates, "meal plan list");
|
||||
state.plan_dates = BTreeSet::from_iter(plan_dates.drain(0..));
|
||||
}
|
||||
|
||||
info!("Synchronizing meal plan");
|
||||
let plan = if let Some(cached_plan_date) = local_store.get_plan_date() {
|
||||
let plan = store.fetch_plan_for_date(&cached_plan_date).await?;
|
||||
state.selected_plan_date = Some(cached_plan_date);
|
||||
plan
|
||||
let plan = if let Some(ref cached_plan_date) = state.selected_plan_date {
|
||||
store
|
||||
.fetch_plan_for_date(cached_plan_date)
|
||||
.await?
|
||||
.or_else(|| Some(Vec::new()))
|
||||
} else {
|
||||
store.fetch_plan().await?
|
||||
None
|
||||
};
|
||||
if let Some(plan) = plan {
|
||||
// set the counts.
|
||||
@ -230,23 +233,13 @@ impl StateMachine {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let Some(plan) = local_store.get_plan() {
|
||||
state.recipe_counts = plan.iter().map(|(k, v)| (k.clone(), *v as usize)).collect();
|
||||
} else {
|
||||
// Initialize things to zero.
|
||||
if let Some(rs) = recipe_entries {
|
||||
for r in rs {
|
||||
state.recipe_counts.insert(r.recipe_id().to_owned(), 0);
|
||||
}
|
||||
// Initialize things to zero.
|
||||
if let Some(rs) = recipe_entries {
|
||||
for r in rs {
|
||||
state.recipe_counts.insert(r.recipe_id().to_owned(), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
let plan = state
|
||||
.recipe_counts
|
||||
.iter()
|
||||
.map(|(k, v)| (k.clone(), *v as i32))
|
||||
.collect::<Vec<(String, i32)>>();
|
||||
local_store.store_plan(&plan);
|
||||
info!("Checking for user account data");
|
||||
if let Some(user_data) = store.fetch_user_data().await {
|
||||
debug!("Successfully got account data from server");
|
||||
@ -261,13 +254,11 @@ impl StateMachine {
|
||||
match store.fetch_categories().await {
|
||||
Ok(Some(mut categories_content)) => {
|
||||
debug!(categories=?categories_content);
|
||||
local_store.set_categories(Some(&categories_content));
|
||||
let category_map = BTreeMap::from_iter(categories_content.drain(0..));
|
||||
state.category_map = category_map;
|
||||
}
|
||||
Ok(None) => {
|
||||
warn!("There is no category file");
|
||||
local_store.set_categories(None);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("{:?}", e);
|
||||
@ -281,11 +272,6 @@ impl StateMachine {
|
||||
info!("Synchronizing inventory data");
|
||||
match inventory_data {
|
||||
Ok((filtered_ingredients, modified_amts, extra_items)) => {
|
||||
local_store.set_inventory_data((
|
||||
&filtered_ingredients,
|
||||
&modified_amts,
|
||||
&extra_items,
|
||||
));
|
||||
state.modified_amts = modified_amts;
|
||||
state.filtered_ingredients = filtered_ingredients;
|
||||
state.extras = extra_items;
|
||||
@ -294,6 +280,8 @@ impl StateMachine {
|
||||
error!("{:?}", e);
|
||||
}
|
||||
}
|
||||
// Finally we store all of this app state back to our localstore
|
||||
local_store.store_app_state(&state);
|
||||
original.set(state);
|
||||
Ok(())
|
||||
}
|
||||
@ -310,35 +298,16 @@ impl MessageMapper<Message, AppState> for StateMachine {
|
||||
for (id, _) in original_copy.recipes.iter() {
|
||||
map.insert(id.clone(), 0);
|
||||
}
|
||||
let plan: Vec<(String, i32)> =
|
||||
map.iter().map(|(s, i)| (s.clone(), *i as i32)).collect();
|
||||
self.local_store.store_plan(&plan);
|
||||
original_copy.recipe_counts = map;
|
||||
}
|
||||
Message::UpdateRecipeCount(id, count) => {
|
||||
original_copy.recipe_counts.insert(id, count);
|
||||
let plan: Vec<(String, i32)> = original_copy
|
||||
.recipe_counts
|
||||
.iter()
|
||||
.map(|(s, i)| (s.clone(), *i as i32))
|
||||
.collect();
|
||||
self.local_store.store_plan(&plan);
|
||||
}
|
||||
Message::AddExtra(amt, name) => {
|
||||
original_copy.extras.push((amt, name));
|
||||
self.local_store.set_inventory_data((
|
||||
&original_copy.filtered_ingredients,
|
||||
&original_copy.modified_amts,
|
||||
&original_copy.extras,
|
||||
))
|
||||
}
|
||||
Message::RemoveExtra(idx) => {
|
||||
original_copy.extras.remove(idx);
|
||||
self.local_store.set_inventory_data((
|
||||
&original_copy.filtered_ingredients,
|
||||
&original_copy.modified_amts,
|
||||
&original_copy.extras,
|
||||
))
|
||||
}
|
||||
Message::UpdateExtra(idx, amt, name) => {
|
||||
match original_copy.extras.get_mut(idx) {
|
||||
@ -350,11 +319,6 @@ impl MessageMapper<Message, AppState> for StateMachine {
|
||||
throw_str("Attempted to remove extra that didn't exist");
|
||||
}
|
||||
}
|
||||
self.local_store.set_inventory_data((
|
||||
&original_copy.filtered_ingredients,
|
||||
&original_copy.modified_amts,
|
||||
&original_copy.extras,
|
||||
))
|
||||
}
|
||||
Message::SaveRecipe(entry, callback) => {
|
||||
let recipe =
|
||||
@ -404,8 +368,6 @@ impl MessageMapper<Message, AppState> for StateMachine {
|
||||
});
|
||||
}
|
||||
Message::UpdateCategory(ingredient, category, callback) => {
|
||||
self.local_store
|
||||
.set_categories(Some(&vec![(ingredient.clone(), category.clone())]));
|
||||
original_copy
|
||||
.category_map
|
||||
.insert(ingredient.clone(), category.clone());
|
||||
@ -421,28 +383,13 @@ impl MessageMapper<Message, AppState> for StateMachine {
|
||||
original_copy.filtered_ingredients = BTreeSet::new();
|
||||
original_copy.modified_amts = BTreeMap::new();
|
||||
original_copy.extras = Vec::new();
|
||||
self.local_store.set_inventory_data((
|
||||
&original_copy.filtered_ingredients,
|
||||
&original_copy.modified_amts,
|
||||
&original_copy.extras,
|
||||
));
|
||||
components::toast::message(cx, "Reset Inventory", None);
|
||||
}
|
||||
Message::AddFilteredIngredient(key) => {
|
||||
original_copy.filtered_ingredients.insert(key);
|
||||
self.local_store.set_inventory_data((
|
||||
&original_copy.filtered_ingredients,
|
||||
&original_copy.modified_amts,
|
||||
&original_copy.extras,
|
||||
));
|
||||
}
|
||||
Message::UpdateAmt(key, amt) => {
|
||||
original_copy.modified_amts.insert(key, amt);
|
||||
self.local_store.set_inventory_data((
|
||||
&original_copy.filtered_ingredients,
|
||||
&original_copy.modified_amts,
|
||||
&original_copy.extras,
|
||||
));
|
||||
}
|
||||
Message::SetUserData(user_data) => {
|
||||
self.local_store.set_user_data(Some(&user_data));
|
||||
@ -451,19 +398,25 @@ impl MessageMapper<Message, AppState> for StateMachine {
|
||||
Message::SaveState(f) => {
|
||||
let mut original_copy = original_copy.clone();
|
||||
let store = self.store.clone();
|
||||
let local_store = self.local_store.clone();
|
||||
spawn_local_scoped(cx, async move {
|
||||
if original_copy.selected_plan_date.is_none() {
|
||||
original_copy.selected_plan_date = Some(chrono::Local::now().date_naive());
|
||||
}
|
||||
original_copy
|
||||
.plan_dates
|
||||
.insert(original_copy.selected_plan_date.map(|d| d.clone()).unwrap());
|
||||
original_copy.plan_dates.insert(
|
||||
original_copy
|
||||
.selected_plan_date
|
||||
.as_ref()
|
||||
.map(|d| d.clone())
|
||||
.unwrap(),
|
||||
);
|
||||
if let Err(e) = store.store_app_state(&original_copy).await {
|
||||
error!(err=?e, "Error saving app state");
|
||||
components::toast::error_message(cx, "Failed to save user state", None);
|
||||
} else {
|
||||
components::toast::message(cx, "Saved user state", None);
|
||||
};
|
||||
local_store.store_app_state(&original_copy);
|
||||
original.set(original_copy);
|
||||
f.map(|f| f());
|
||||
});
|
||||
@ -474,17 +427,13 @@ impl MessageMapper<Message, AppState> for StateMachine {
|
||||
Message::LoadState(f) => {
|
||||
let store = self.store.clone();
|
||||
let local_store = self.local_store.clone();
|
||||
debug!("Loading user state.");
|
||||
spawn_local_scoped(cx, async move {
|
||||
if let Err(err) = Self::load_state(&store, &local_store, original).await {
|
||||
error!(?err, "Failed to load user state");
|
||||
components::toast::error_message(cx, "Failed to load_state.", None);
|
||||
} else {
|
||||
components::toast::message(cx, "Loaded user state", None);
|
||||
local_store.set_inventory_data((
|
||||
&original.get().filtered_ingredients,
|
||||
&original.get().modified_amts,
|
||||
&original.get().extras,
|
||||
));
|
||||
}
|
||||
f.map(|f| f());
|
||||
});
|
||||
@ -492,9 +441,7 @@ impl MessageMapper<Message, AppState> for StateMachine {
|
||||
}
|
||||
Message::UpdateStaples(content, callback) => {
|
||||
let store = self.store.clone();
|
||||
let local_store = self.local_store.clone();
|
||||
spawn_local_scoped(cx, async move {
|
||||
local_store.set_staples(&content);
|
||||
if let Err(err) = store.store_staples(content).await {
|
||||
error!(?err, "Failed to store staples");
|
||||
components::toast::error_message(cx, "Failed to store staples", None);
|
||||
@ -507,7 +454,7 @@ impl MessageMapper<Message, AppState> for StateMachine {
|
||||
}
|
||||
Message::SelectPlanDate(date, callback) => {
|
||||
let store = self.store.clone();
|
||||
let local_store = self.local_store.clone();
|
||||
let local_store = self.local_store.clone();
|
||||
spawn_local_scoped(cx, async move {
|
||||
if let Some(mut plan) = store
|
||||
.fetch_plan_for_date(&date)
|
||||
@ -528,12 +475,11 @@ impl MessageMapper<Message, AppState> for StateMachine {
|
||||
original_copy.filtered_ingredients = filtered;
|
||||
original_copy.extras = extras;
|
||||
original_copy.selected_plan_date = Some(date.clone());
|
||||
local_store.set_plan_date(&date);
|
||||
store
|
||||
.store_plan_for_date(vec![], &date)
|
||||
.await
|
||||
.expect("Failed to init meal plan for date");
|
||||
|
||||
local_store.store_app_state(&original_copy);
|
||||
original.set(original_copy);
|
||||
|
||||
callback.map(|f| f());
|
||||
@ -545,7 +491,7 @@ impl MessageMapper<Message, AppState> for StateMachine {
|
||||
}
|
||||
Message::DeletePlan(date, callback) => {
|
||||
let store = self.store.clone();
|
||||
let local_store = self.local_store.clone();
|
||||
let local_store = self.local_store.clone();
|
||||
spawn_local_scoped(cx, async move {
|
||||
if let Err(err) = store.delete_plan_for_date(&date).await {
|
||||
components::toast::error_message(
|
||||
@ -555,23 +501,26 @@ impl MessageMapper<Message, AppState> for StateMachine {
|
||||
);
|
||||
error!(?err, "Error deleting plan");
|
||||
} else {
|
||||
local_store.delete_plan();
|
||||
|
||||
original_copy.plan_dates.remove(&date);
|
||||
// Reset all meal planning state;
|
||||
let _ = original_copy.recipe_counts.iter_mut().map(|(_, v)| *v = 0);
|
||||
original_copy.filtered_ingredients = BTreeSet::new();
|
||||
original_copy.modified_amts = BTreeMap::new();
|
||||
original_copy.extras = Vec::new();
|
||||
local_store.store_app_state(&original_copy);
|
||||
original.set(original_copy);
|
||||
components::toast::message(cx, "Deleted Plan", None);
|
||||
|
||||
callback.map(|f| f());
|
||||
}
|
||||
});
|
||||
// NOTE(jwall): Because we do our signal set above in the async block
|
||||
// we have to return here to avoid lifetime issues and double setting
|
||||
// the original signal.
|
||||
return;
|
||||
}
|
||||
}
|
||||
self.local_store.store_app_state(&original_copy);
|
||||
original.set(original_copy);
|
||||
}
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ pub fn RecipePlan<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> Vie
|
||||
.get()
|
||||
.recipes
|
||||
.get(r)
|
||||
.expect("Failed to find recipe")
|
||||
.expect(&format!("Failed to find recipe {}", r))
|
||||
.clone(),
|
||||
));
|
||||
map
|
||||
|
Loading…
x
Reference in New Issue
Block a user