mirror of
https://github.com/zaphar/kitchen.git
synced 2025-07-22 19:40:14 -04:00
On the fly recipe parse checks
This commit is contained in:
parent
829b81d3e2
commit
7559aae9d0
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -1319,6 +1319,7 @@ dependencies = [
|
|||||||
"base64 0.20.0",
|
"base64 0.20.0",
|
||||||
"chrono",
|
"chrono",
|
||||||
"console_error_panic_hook",
|
"console_error_panic_hook",
|
||||||
|
"js-sys",
|
||||||
"recipes",
|
"recipes",
|
||||||
"reqwasm",
|
"reqwasm",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -23,6 +23,7 @@ tracing = "0.1.35"
|
|||||||
async-trait = "0.1.57"
|
async-trait = "0.1.57"
|
||||||
base64 = "0.20.0"
|
base64 = "0.20.0"
|
||||||
sycamore-router = "0.8"
|
sycamore-router = "0.8"
|
||||||
|
js-sys = "0.3.60"
|
||||||
|
|
||||||
[dependencies.tracing-subscriber]
|
[dependencies.tracing-subscriber]
|
||||||
version = "0.3.16"
|
version = "0.3.16"
|
||||||
|
@ -59,17 +59,17 @@ pub enum Message {
|
|||||||
AddExtra(String, String),
|
AddExtra(String, String),
|
||||||
RemoveExtra(usize),
|
RemoveExtra(usize),
|
||||||
UpdateExtra(usize, String, String),
|
UpdateExtra(usize, String, String),
|
||||||
SaveRecipe(RecipeEntry),
|
SaveRecipe(RecipeEntry, Option<Box<dyn FnOnce()>>),
|
||||||
SetRecipe(String, Recipe),
|
SetRecipe(String, Recipe),
|
||||||
RemoveRecipe(String),
|
RemoveRecipe(String, Option<Box<dyn FnOnce()>>),
|
||||||
UpdateCategory(String, String),
|
UpdateCategory(String, String, Option<Box<dyn FnOnce()>>),
|
||||||
ResetInventory,
|
ResetInventory,
|
||||||
AddFilteredIngredient(IngredientKey),
|
AddFilteredIngredient(IngredientKey),
|
||||||
UpdateAmt(IngredientKey, String),
|
UpdateAmt(IngredientKey, String),
|
||||||
SetUserData(UserData),
|
SetUserData(UserData),
|
||||||
SaveState(Option<Box<dyn FnOnce()>>),
|
SaveState(Option<Box<dyn FnOnce()>>),
|
||||||
LoadState(Option<Box<dyn FnOnce()>>),
|
LoadState(Option<Box<dyn FnOnce()>>),
|
||||||
UpdateStaples(String),
|
UpdateStaples(String, Option<Box<dyn FnOnce()>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for Message {
|
impl Debug for Message {
|
||||||
@ -91,12 +91,12 @@ impl Debug for Message {
|
|||||||
.field(arg1)
|
.field(arg1)
|
||||||
.field(arg2)
|
.field(arg2)
|
||||||
.finish(),
|
.finish(),
|
||||||
Self::SaveRecipe(arg0) => f.debug_tuple("SaveRecipe").field(arg0).finish(),
|
Self::SaveRecipe(arg0, _) => f.debug_tuple("SaveRecipe").field(arg0).finish(),
|
||||||
Self::SetRecipe(arg0, arg1) => {
|
Self::SetRecipe(arg0, arg1) => {
|
||||||
f.debug_tuple("SetRecipe").field(arg0).field(arg1).finish()
|
f.debug_tuple("SetRecipe").field(arg0).field(arg1).finish()
|
||||||
}
|
}
|
||||||
Self::RemoveRecipe(arg0) => f.debug_tuple("SetCategoryMap").field(arg0).finish(),
|
Self::RemoveRecipe(arg0, _) => f.debug_tuple("SetCategoryMap").field(arg0).finish(),
|
||||||
Self::UpdateCategory(i, c) => {
|
Self::UpdateCategory(i, c, _) => {
|
||||||
f.debug_tuple("UpdateCategory").field(i).field(c).finish()
|
f.debug_tuple("UpdateCategory").field(i).field(c).finish()
|
||||||
}
|
}
|
||||||
Self::ResetInventory => write!(f, "ResetInventory"),
|
Self::ResetInventory => write!(f, "ResetInventory"),
|
||||||
@ -109,7 +109,7 @@ impl Debug for Message {
|
|||||||
Self::SetUserData(arg0) => f.debug_tuple("SetUserData").field(arg0).finish(),
|
Self::SetUserData(arg0) => f.debug_tuple("SetUserData").field(arg0).finish(),
|
||||||
Self::SaveState(_) => write!(f, "SaveState"),
|
Self::SaveState(_) => write!(f, "SaveState"),
|
||||||
Self::LoadState(_) => write!(f, "LoadState"),
|
Self::LoadState(_) => write!(f, "LoadState"),
|
||||||
Self::UpdateStaples(arg) => f.debug_tuple("UpdateStaples").field(arg).finish(),
|
Self::UpdateStaples(arg, _) => f.debug_tuple("UpdateStaples").field(arg).finish(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -312,7 +312,7 @@ impl MessageMapper<Message, AppState> for StateMachine {
|
|||||||
Message::SetRecipe(id, recipe) => {
|
Message::SetRecipe(id, recipe) => {
|
||||||
original_copy.recipes.insert(id, recipe);
|
original_copy.recipes.insert(id, recipe);
|
||||||
}
|
}
|
||||||
Message::SaveRecipe(entry) => {
|
Message::SaveRecipe(entry, callback) => {
|
||||||
let recipe =
|
let recipe =
|
||||||
parse::as_recipe(entry.recipe_text()).expect("Failed to parse RecipeEntry");
|
parse::as_recipe(entry.recipe_text()).expect("Failed to parse RecipeEntry");
|
||||||
original_copy
|
original_copy
|
||||||
@ -327,9 +327,10 @@ impl MessageMapper<Message, AppState> for StateMachine {
|
|||||||
if let Err(e) = store.store_recipes(vec![entry]).await {
|
if let Err(e) = store.store_recipes(vec![entry]).await {
|
||||||
error!(err=?e, "Unable to save Recipe");
|
error!(err=?e, "Unable to save Recipe");
|
||||||
}
|
}
|
||||||
|
callback.map(|f| f());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Message::RemoveRecipe(recipe) => {
|
Message::RemoveRecipe(recipe, callback) => {
|
||||||
original_copy.recipe_counts.remove(&recipe);
|
original_copy.recipe_counts.remove(&recipe);
|
||||||
original_copy.recipes.remove(&recipe);
|
original_copy.recipes.remove(&recipe);
|
||||||
self.local_store.delete_recipe_entry(&recipe);
|
self.local_store.delete_recipe_entry(&recipe);
|
||||||
@ -338,9 +339,10 @@ impl MessageMapper<Message, AppState> for StateMachine {
|
|||||||
if let Err(err) = store.delete_recipe(&recipe).await {
|
if let Err(err) = store.delete_recipe(&recipe).await {
|
||||||
error!(?err, "Failed to delete recipe");
|
error!(?err, "Failed to delete recipe");
|
||||||
}
|
}
|
||||||
|
callback.map(|f| f());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Message::UpdateCategory(ingredient, category) => {
|
Message::UpdateCategory(ingredient, category, callback) => {
|
||||||
self.local_store
|
self.local_store
|
||||||
.set_categories(Some(&vec![(ingredient.clone(), category.clone())]));
|
.set_categories(Some(&vec![(ingredient.clone(), category.clone())]));
|
||||||
original_copy
|
original_copy
|
||||||
@ -351,6 +353,7 @@ impl MessageMapper<Message, AppState> for StateMachine {
|
|||||||
if let Err(e) = store.store_categories(&vec![(ingredient, category)]).await {
|
if let Err(e) = store.store_categories(&vec![(ingredient, category)]).await {
|
||||||
error!(?e, "Failed to save categories");
|
error!(?e, "Failed to save categories");
|
||||||
}
|
}
|
||||||
|
callback.map(|f| f());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Message::ResetInventory => {
|
Message::ResetInventory => {
|
||||||
@ -409,7 +412,7 @@ impl MessageMapper<Message, AppState> for StateMachine {
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Message::UpdateStaples(content) => {
|
Message::UpdateStaples(content, 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 {
|
||||||
@ -418,6 +421,7 @@ impl MessageMapper<Message, AppState> for StateMachine {
|
|||||||
.store_staples(content)
|
.store_staples(content)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to store staples");
|
.expect("Failed to store staples");
|
||||||
|
callback.map(|f| f());
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -77,9 +77,10 @@ pub fn AddRecipe<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View
|
|||||||
error!(?err)
|
error!(?err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sh.dispatch(cx, Message::SaveRecipe((*entry).clone()));
|
sh.dispatch(cx, Message::SaveRecipe((*entry).clone(), Some(Box::new({
|
||||||
crate::js_lib::navigate_to_path(&format!("/ui/recipe/edit/{}", entry.recipe_id()))
|
let path = format!("/ui/recipe/edit/{}", entry.recipe_id());
|
||||||
.expect("Unable to navigate to recipe");
|
move || sycamore_router::navigate(path.as_str())
|
||||||
|
}))));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}) { "Create" }
|
}) { "Create" }
|
||||||
|
@ -69,7 +69,7 @@ fn CategoryRow<'ctx, G: Html>(cx: Scope<'ctx>, props: CategoryRowProps<'ctx>) ->
|
|||||||
td() { input(type="text", list="category_options", bind:value=category, on:change={
|
td() { input(type="text", list="category_options", bind:value=category, on:change={
|
||||||
let ingredient_clone = ingredient.clone();
|
let ingredient_clone = ingredient.clone();
|
||||||
move |_| {
|
move |_| {
|
||||||
sh.dispatch(cx, Message::UpdateCategory(ingredient_clone.clone(), category.get_untracked().as_ref().clone()));
|
sh.dispatch(cx, Message::UpdateCategory(ingredient_clone.clone(), category.get_untracked().as_ref().clone(), None));
|
||||||
}
|
}
|
||||||
}) }
|
}) }
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,10 @@
|
|||||||
use sycamore::{futures::spawn_local_scoped, prelude::*};
|
use sycamore::{futures::spawn_local_scoped, prelude::*};
|
||||||
use tracing::{debug, error};
|
use tracing::{debug, error};
|
||||||
|
|
||||||
use crate::app_state::{Message, StateHandler};
|
use crate::{
|
||||||
|
app_state::{Message, StateHandler},
|
||||||
|
js_lib,
|
||||||
|
};
|
||||||
use recipes::{self, RecipeEntry};
|
use recipes::{self, RecipeEntry};
|
||||||
|
|
||||||
fn check_recipe_parses(
|
fn check_recipe_parses(
|
||||||
@ -68,21 +71,25 @@ pub fn Editor<'ctx, G: Html>(cx: Scope<'ctx>, props: RecipeComponentProps<'ctx>)
|
|||||||
|
|
||||||
let id = create_memo(cx, || recipe.get().recipe_id().to_owned());
|
let id = create_memo(cx, || recipe.get().recipe_id().to_owned());
|
||||||
let dirty = create_signal(cx, false);
|
let dirty = create_signal(cx, false);
|
||||||
|
let ts = create_signal(cx, js_lib::get_ms_timestamp());
|
||||||
|
|
||||||
debug!("creating editor view");
|
debug!("creating editor view");
|
||||||
view! {cx,
|
view! {cx,
|
||||||
div(class="grid") {
|
div(class="grid") {
|
||||||
textarea(bind:value=text, aria-invalid=aria_hint.get(), rows=20, on:change=move |_| {
|
textarea(bind:value=text, aria-invalid=aria_hint.get(), rows=20, on:change=move |_| {
|
||||||
dirty.set(true);
|
dirty.set(true);
|
||||||
|
check_recipe_parses(text.get_untracked().as_str(), error_text, aria_hint);
|
||||||
|
}, on:input=move |_| {
|
||||||
|
let current_ts = js_lib::get_ms_timestamp();
|
||||||
|
if (current_ts - *ts.get_untracked()) > 100 {
|
||||||
|
check_recipe_parses(text.get_untracked().as_str(), error_text, aria_hint);
|
||||||
|
ts.set(current_ts);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
div(class="parse") { (error_text.get()) }
|
div(class="parse") { (error_text.get()) }
|
||||||
}
|
}
|
||||||
span(role="button", on:click=move |_| {
|
span(role="button", on:click=move |_| {
|
||||||
let unparsed = text.get();
|
let unparsed = text.get_untracked();
|
||||||
check_recipe_parses(unparsed.as_str(), error_text, aria_hint);
|
|
||||||
}) { "Check" } " "
|
|
||||||
span(role="button", on:click=move |_| {
|
|
||||||
let unparsed = text.get();
|
|
||||||
if check_recipe_parses(unparsed.as_str(), error_text, aria_hint) {
|
if check_recipe_parses(unparsed.as_str(), error_text, aria_hint) {
|
||||||
debug!("triggering a save");
|
debug!("triggering a save");
|
||||||
if !*dirty.get_untracked() {
|
if !*dirty.get_untracked() {
|
||||||
@ -119,9 +126,8 @@ pub fn Editor<'ctx, G: Html>(cx: Scope<'ctx>, props: RecipeComponentProps<'ctx>)
|
|||||||
}
|
}
|
||||||
}) { "Save" } " "
|
}) { "Save" } " "
|
||||||
span(role="button", on:click=move |_| {
|
span(role="button", on:click=move |_| {
|
||||||
sh.dispatch(cx, Message::RemoveRecipe(id.get_untracked().as_ref().to_owned()));
|
sh.dispatch(cx, Message::RemoveRecipe(id.get_untracked().as_ref().to_owned(), Some(Box::new(|| sycamore_router::navigate("/ui/planning/plan")))));
|
||||||
sycamore_router::navigate("/ui/planning/plan")
|
}) { "delete" } " "
|
||||||
}) { "delete" }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ use sycamore::{futures::spawn_local_scoped, prelude::*};
|
|||||||
use tracing::{debug, error};
|
use tracing::{debug, error};
|
||||||
|
|
||||||
use crate::app_state::{Message, StateHandler};
|
use crate::app_state::{Message, StateHandler};
|
||||||
|
use crate::js_lib;
|
||||||
use recipes::{self, parse};
|
use recipes::{self, parse};
|
||||||
|
|
||||||
fn check_ingredients_parses(
|
fn check_ingredients_parses(
|
||||||
@ -67,19 +68,22 @@ pub fn IngredientsEditor<'ctx, G: Html>(
|
|||||||
});
|
});
|
||||||
|
|
||||||
let dirty = create_signal(cx, false);
|
let dirty = create_signal(cx, false);
|
||||||
|
let ts = create_signal(cx, js_lib::get_ms_timestamp());
|
||||||
|
|
||||||
debug!("creating editor view");
|
debug!("creating editor view");
|
||||||
view! {cx,
|
view! {cx,
|
||||||
div(class="grid") {
|
div(class="grid") {
|
||||||
textarea(bind:value=text, aria-invalid=aria_hint.get(), rows=20, on:change=move |_| {
|
textarea(bind:value=text, aria-invalid=aria_hint.get(), rows=20, on:change=move |_| {
|
||||||
dirty.set(true);
|
dirty.set(true);
|
||||||
|
}, on:input=move |_| {
|
||||||
|
let current_ts = js_lib::get_ms_timestamp();
|
||||||
|
if (current_ts - *ts.get_untracked()) > 100 {
|
||||||
|
check_ingredients_parses(text.get_untracked().as_str(), error_text, aria_hint);
|
||||||
|
ts.set(current_ts);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
div(class="parse") { (error_text.get()) }
|
div(class="parse") { (error_text.get()) }
|
||||||
}
|
}
|
||||||
span(role="button", on:click=move |_| {
|
|
||||||
let unparsed = text.get();
|
|
||||||
check_ingredients_parses(unparsed.as_str(), error_text, aria_hint);
|
|
||||||
}) { "Check" } " "
|
|
||||||
span(role="button", on:click=move |_| {
|
span(role="button", on:click=move |_| {
|
||||||
let unparsed = text.get();
|
let unparsed = text.get();
|
||||||
if !*dirty.get_untracked() {
|
if !*dirty.get_untracked() {
|
||||||
@ -89,7 +93,7 @@ pub fn IngredientsEditor<'ctx, G: Html>(
|
|||||||
debug!("triggering a save");
|
debug!("triggering a save");
|
||||||
if check_ingredients_parses(unparsed.as_str(), error_text, aria_hint) {
|
if check_ingredients_parses(unparsed.as_str(), error_text, aria_hint) {
|
||||||
debug!("Staples text is changed");
|
debug!("Staples text is changed");
|
||||||
sh.dispatch(cx, Message::UpdateStaples(unparsed.as_ref().clone()));
|
sh.dispatch(cx, Message::UpdateStaples(unparsed.as_ref().clone(), None));
|
||||||
}
|
}
|
||||||
}) { "Save" }
|
}) { "Save" }
|
||||||
}
|
}
|
||||||
|
@ -11,6 +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 js_sys::Date;
|
||||||
use wasm_bindgen::JsValue;
|
use wasm_bindgen::JsValue;
|
||||||
use web_sys::{window, Storage};
|
use web_sys::{window, Storage};
|
||||||
|
|
||||||
@ -28,3 +29,7 @@ pub fn get_storage() -> Storage {
|
|||||||
.expect("Failed to get storage")
|
.expect("Failed to get storage")
|
||||||
.expect("No storage available")
|
.expect("No storage available")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_ms_timestamp() -> u32 {
|
||||||
|
Date::new_0().get_milliseconds()
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user