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