From d4962c98ff08483bfa91a1df21a7f7398b41bbb0 Mon Sep 17 00:00:00 2001 From: Greg Burri Date: Fri, 27 Dec 2024 12:51:29 +0100 Subject: [PATCH] Recipe edit (WIP): Buttons to add steps and inrgedients --- Cargo.lock | 4 +-- backend/sql/version_1.sql | 2 +- backend/src/data/db/recipe.rs | 34 ++++++++++++++++++ backend/src/data/model.rs | 2 +- backend/src/main.rs | 10 ++++++ backend/src/services/ron.rs | 52 ++++++++++++++++++++++++++++ backend/templates/recipe_edit.html | 6 ++-- common/src/ron_api.rs | 32 ++++++++++++++++- frontend/src/handles.rs | 55 ++++++++++++++++++++++++++---- 9 files changed, 182 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ca1f318..de94fa4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -391,9 +391,9 @@ checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "cc" -version = "1.2.5" +version = "1.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e" +checksum = "8d6dbb628b8f8555f86d0323c2eb39e3ec81901f4b83e091db8a6a76d316a333" dependencies = [ "shlex", ] diff --git a/backend/sql/version_1.sql b/backend/sql/version_1.sql index 1efde53..48b063c 100644 --- a/backend/sql/version_1.sql +++ b/backend/sql/version_1.sql @@ -121,7 +121,7 @@ CREATE TABLE [Ingredient] ( [step_id] INTEGER NOT NULL, - [name] TEXT NOT NULL, + [name] TEXT NOT NULL DEFAULT '', [comment] TEXT NOT NULL DEFAULT '', [quantity_value] REAL, [quantity_unit] TEXT NOT NULL DEFAULT '', diff --git a/backend/src/data/db/recipe.rs b/backend/src/data/db/recipe.rs index d112b12..e97c28b 100644 --- a/backend/src/data/db/recipe.rs +++ b/backend/src/data/db/recipe.rs @@ -304,6 +304,23 @@ ORDER BY [name] .map_err(DBError::from) } + pub async fn add_recipe_step(&self, group_id: i64) -> Result { + let db_result = sqlx::query("INSERT INTO [Step] ([group_id]) VALUES ($1)") + .bind(group_id) + .execute(&self.pool) + .await?; + Ok(db_result.last_insert_rowid()) + } + + pub async fn rm_recipe_step(&self, step_id: i64) -> Result<()> { + sqlx::query("DELETE FROM [Step] WHERE [id] = $1") + .bind(step_id) + .execute(&self.pool) + .await + .map(|_| ()) + .map_err(DBError::from) + } + pub async fn set_step_action(&self, step_id: i64, action: &str) -> Result<()> { sqlx::query("UPDATE [Step] SET [action] = $2 WHERE [id] = $1") .bind(step_id) @@ -314,6 +331,23 @@ ORDER BY [name] .map_err(DBError::from) } + pub async fn add_recipe_ingredient(&self, step_id: i64) -> Result { + let db_result = sqlx::query("INSERT INTO [Ingredient] ([step_id]) VALUES ($1)") + .bind(step_id) + .execute(&self.pool) + .await?; + Ok(db_result.last_insert_rowid()) + } + + pub async fn rm_recipe_ingredient(&self, ingredient_id: i64) -> Result<()> { + sqlx::query("DELETE FROM [Ingredient] WHERE [id] = $1") + .bind(ingredient_id) + .execute(&self.pool) + .await + .map(|_| ()) + .map_err(DBError::from) + } + pub async fn set_ingredient_name(&self, ingredient_id: i64, name: &str) -> Result<()> { sqlx::query("UPDATE [Ingredient] SET [name] = $2 WHERE [id] = $1") .bind(ingredient_id) diff --git a/backend/src/data/model.rs b/backend/src/data/model.rs index 84bad3c..d837393 100644 --- a/backend/src/data/model.rs +++ b/backend/src/data/model.rs @@ -58,6 +58,6 @@ pub struct Ingredient { pub id: i64, pub name: String, pub comment: String, - pub quantity_value: f64, + pub quantity_value: Option, pub quantity_unit: String, } diff --git a/backend/src/main.rs b/backend/src/main.rs index 46ebe06..96f9066 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -109,10 +109,20 @@ async fn main() { "/recipe/set_group_comment", put(services::ron::set_group_comment), ) + .route("/recipe/add_step", post(services::ron::add_step)) + .route("/recipe/remove_step", delete(services::ron::rm_step)) .route( "/recipe/set_step_action", put(services::ron::set_step_action), ) + .route( + "/recipe/add_ingredient", + post(services::ron::add_ingredient), + ) + .route( + "/recipe/remove_ingredient", + delete(services::ron::rm_ingredient), + ) .route( "/recipe/set_ingredient_name", put(services::ron::set_ingredient_name), diff --git a/backend/src/services/ron.rs b/backend/src/services/ron.rs index 12c0f76..8f8ea96 100644 --- a/backend/src/services/ron.rs +++ b/backend/src/services/ron.rs @@ -355,6 +355,32 @@ pub async fn set_group_comment( Ok(StatusCode::OK) } +#[debug_handler] +pub async fn add_step( + State(connection): State, + Extension(user): Extension>, + ExtractRon(ron): ExtractRon, +) -> Result { + check_user_rights_recipe_group(&connection, &user, ron.group_id).await?; + let step_id = connection.add_recipe_step(ron.group_id).await?; + + Ok(ron_response( + StatusCode::OK, + common::ron_api::AddRecipeStepResult { step_id }, + )) +} + +#[debug_handler] +pub async fn rm_step( + State(connection): State, + Extension(user): Extension>, + ExtractRon(ron): ExtractRon, +) -> Result { + check_user_rights_recipe_step(&connection, &user, ron.step_id).await?; + connection.rm_recipe_step(ron.step_id).await?; + Ok(StatusCode::OK) +} + #[debug_handler] pub async fn set_step_action( State(connection): State, @@ -366,6 +392,32 @@ pub async fn set_step_action( Ok(StatusCode::OK) } +#[debug_handler] +pub async fn add_ingredient( + State(connection): State, + Extension(user): Extension>, + ExtractRon(ron): ExtractRon, +) -> Result { + check_user_rights_recipe_step(&connection, &user, ron.step_id).await?; + let ingredient_id = connection.add_recipe_ingredient(ron.step_id).await?; + + Ok(ron_response( + StatusCode::OK, + common::ron_api::AddRecipeIngredientResult { ingredient_id }, + )) +} + +#[debug_handler] +pub async fn rm_ingredient( + State(connection): State, + Extension(user): Extension>, + ExtractRon(ron): ExtractRon, +) -> Result { + check_user_rights_recipe_ingredient(&connection, &user, ron.ingredient_id).await?; + connection.rm_recipe_ingredient(ron.ingredient_id).await?; + Ok(StatusCode::OK) +} + #[debug_handler] pub async fn set_ingredient_name( State(connection): State, diff --git a/backend/templates/recipe_edit.html b/backend/templates/recipe_edit.html index 8b3c888..5438d3c 100644 --- a/backend/templates/recipe_edit.html +++ b/backend/templates/recipe_edit.html @@ -64,7 +64,7 @@
- +
@@ -78,7 +78,7 @@
- +
@@ -89,7 +89,7 @@
- +
diff --git a/common/src/ron_api.rs b/common/src/ron_api.rs index 2b8b9cb..cdaba4d 100644 --- a/common/src/ron_api.rs +++ b/common/src/ron_api.rs @@ -92,12 +92,42 @@ pub struct SetGroupComment { pub comment: String, } +#[derive(Serialize, Deserialize, Clone)] +pub struct AddRecipeStep { + pub group_id: i64, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct AddRecipeStepResult { + pub step_id: i64, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct RemoveRecipeStep { + pub step_id: i64, +} + #[derive(Serialize, Deserialize, Clone)] pub struct SetStepAction { pub step_id: i64, pub action: String, } +#[derive(Serialize, Deserialize, Clone)] +pub struct AddRecipeIngredient { + pub step_id: i64, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct AddRecipeIngredientResult { + pub ingredient_id: i64, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct RemoveRecipeIngredient { + pub ingredient_id: i64, +} + #[derive(Serialize, Deserialize, Clone)] pub struct SetIngredientName { pub ingredient_id: i64, @@ -142,7 +172,7 @@ pub struct Ingredient { pub id: i64, pub name: String, pub comment: String, - pub quantity_value: f64, + pub quantity_value: Option, pub quantity_unit: String, } diff --git a/frontend/src/handles.rs b/frontend/src/handles.rs index 36e0de1..20e70ae 100644 --- a/frontend/src/handles.rs +++ b/frontend/src/handles.rs @@ -220,6 +220,25 @@ pub fn recipe_edit(recipe_id: i64) -> Result<(), JsValue> { }) .forget(); + // Add step button. + let add_step_button: HtmlInputElement = group_element.select(".input-add-step"); + EventListener::new(&add_step_button, "click", move |_event| { + spawn_local(async move { + let body = ron_api::AddRecipeStep { group_id }; + let response: ron_api::AddRecipeStepResult = + request::post("recipe/add_step", body).await.unwrap(); + create_step_element( + &by_id::(&format!("group-{}", group_id)), + &ron_api::Step { + id: response.step_id, + action: "".to_string(), + ingredients: vec![], + }, + ); + }); + }) + .forget(); + group_element } @@ -249,6 +268,27 @@ pub fn recipe_edit(recipe_id: i64) -> Result<(), JsValue> { }) .forget(); + // Add ingredient button. + let add_ingredient_button: HtmlInputElement = step_element.select(".input-add-ingredient"); + EventListener::new(&add_ingredient_button, "click", move |_event| { + spawn_local(async move { + let body = ron_api::AddRecipeIngredient { step_id }; + let response: ron_api::AddRecipeIngredientResult = + request::post("recipe/add_ingredient", body).await.unwrap(); + create_ingredient_element( + &by_id::(&format!("step-{}", step_id)), + &ron_api::Ingredient { + id: response.ingredient_id, + name: "".to_string(), + comment: "".to_string(), + quantity_value: None, + quantity_unit: "".to_string(), + }, + ); + }); + }) + .forget(); + step_element } @@ -259,7 +299,7 @@ pub fn recipe_edit(recipe_id: i64) -> Result<(), JsValue> { let ingredient_id = ingredient.id; let ingredient_element: Element = select_and_clone("#hidden-templates .ingredient"); ingredient_element - .set_attribute("id", &format!("step-{}", ingredient.id)) + .set_attribute("id", &format!("ingredient-{}", ingredient.id)) .unwrap(); step_element.append_child(&ingredient_element).unwrap(); @@ -301,8 +341,12 @@ pub fn recipe_edit(recipe_id: i64) -> Result<(), JsValue> { // Ingredient quantity. let quantity: HtmlInputElement = ingredient_element.select(".input-ingredient-quantity"); - quantity.set_value(&ingredient.quantity_value.to_string()); - let mut current_quantity = ingredient.quantity_value; + quantity.set_value( + &ingredient + .quantity_value + .map_or("".to_string(), |q| q.to_string()), + ); + let mut current_quantity = quantity.value_as_number(); EventListener::new(&quantity.clone(), "blur", move |_event| { let n = quantity.value_as_number(); if n.is_nan() { @@ -367,11 +411,9 @@ pub fn recipe_edit(recipe_id: i64) -> Result<(), JsValue> { // Add a new group. { - let button_add_group = document().get_element_by_id("button-add-group").unwrap(); + let button_add_group: HtmlInputElement = by_id("input-add-group"); let on_click_add_group = EventListener::new(&button_add_group, "click", move |_event| { - log!("Click!"); let body = ron_api::AddRecipeGroup { recipe_id }; - spawn_local(async move { let response: ron_api::AddRecipeGroupResult = request::post("recipe/add_group", body).await.unwrap(); @@ -381,7 +423,6 @@ pub fn recipe_edit(recipe_id: i64) -> Result<(), JsValue> { comment: "".to_string(), steps: vec![], }); - // group_html.set_attribute("id", "test").unwrap(); }); }); on_click_add_group.forget(); -- 2.49.0