Add a way to delete recipe
This commit is contained in:
parent
5ce3391466
commit
31bc31035a
10 changed files with 247 additions and 175 deletions
|
|
@ -19,6 +19,6 @@ pub const SEND_EMAIL_TIMEOUT: Duration = Duration::from_secs(60);
|
||||||
// Common headers can be found in 'axum::http::header' (which is a re-export of the create 'http').
|
// Common headers can be found in 'axum::http::header' (which is a re-export of the create 'http').
|
||||||
pub const REVERSE_PROXY_IP_HTTP_FIELD: &str = "x-real-ip"; // Set by the reverse proxy (Nginx).
|
pub const REVERSE_PROXY_IP_HTTP_FIELD: &str = "x-real-ip"; // Set by the reverse proxy (Nginx).
|
||||||
|
|
||||||
pub const MAX_DB_CONNECTION: u32 = 1024;
|
pub const MAX_DB_CONNECTION: u32 = 1; // To avoid database lock.
|
||||||
|
|
||||||
pub const LANGUAGES: [(&str, &str); 2] = [("Français", "fr"), ("English", "en")];
|
pub const LANGUAGES: [(&str, &str); 2] = [("Français", "fr"), ("English", "en")];
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,11 @@ use std::{
|
||||||
io::Read,
|
io::Read,
|
||||||
path::Path,
|
path::Path,
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use sqlx::{
|
use sqlx::{
|
||||||
sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions},
|
sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions, SqliteSynchronous},
|
||||||
Pool, Sqlite, Transaction,
|
Pool, Sqlite, Transaction,
|
||||||
};
|
};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
@ -75,8 +76,9 @@ impl Connection {
|
||||||
))?
|
))?
|
||||||
.journal_mode(SqliteJournalMode::Wal) // TODO: use 'Wal2' when available.
|
.journal_mode(SqliteJournalMode::Wal) // TODO: use 'Wal2' when available.
|
||||||
.create_if_missing(true)
|
.create_if_missing(true)
|
||||||
.pragma("foreign_keys", "ON")
|
.busy_timeout(Duration::from_secs(10))
|
||||||
.pragma("synchronous", "NORMAL");
|
.foreign_keys(true)
|
||||||
|
.synchronous(SqliteSynchronous::Normal);
|
||||||
|
|
||||||
Self::create_connection(
|
Self::create_connection(
|
||||||
SqlitePoolOptions::new()
|
SqlitePoolOptions::new()
|
||||||
|
|
|
||||||
|
|
@ -37,18 +37,20 @@ ORDER BY [title]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn can_edit_recipe(&self, user_id: i64, recipe_id: i64) -> Result<bool> {
|
pub async fn can_edit_recipe(&self, user_id: i64, recipe_id: i64) -> Result<bool> {
|
||||||
sqlx::query_scalar(r#"SELECT COUNT(*) FROM [Recipe] WHERE [id] = $1 AND [user_id] = $2"#)
|
sqlx::query_scalar(
|
||||||
.bind(recipe_id)
|
r#"SELECT COUNT(*) = 1 FROM [Recipe] WHERE [id] = $1 AND [user_id] = $2"#,
|
||||||
.bind(user_id)
|
)
|
||||||
.fetch_one(&self.pool)
|
.bind(recipe_id)
|
||||||
.await
|
.bind(user_id)
|
||||||
.map_err(DBError::from)
|
.fetch_one(&self.pool)
|
||||||
|
.await
|
||||||
|
.map_err(DBError::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn can_edit_recipe_group(&self, user_id: i64, group_id: i64) -> Result<bool> {
|
pub async fn can_edit_recipe_group(&self, user_id: i64, group_id: i64) -> Result<bool> {
|
||||||
sqlx::query_scalar(
|
sqlx::query_scalar(
|
||||||
r#"
|
r#"
|
||||||
SELECT COUNT(*)
|
SELECT COUNT(*) = 1
|
||||||
FROM [Recipe]
|
FROM [Recipe]
|
||||||
INNER JOIN [Group] ON [Group].[recipe_id] = [Recipe].[id]
|
INNER JOIN [Group] ON [Group].[recipe_id] = [Recipe].[id]
|
||||||
WHERE [Group].[id] = $1 AND [user_id] = $2
|
WHERE [Group].[id] = $1 AND [user_id] = $2
|
||||||
|
|
@ -64,7 +66,7 @@ WHERE [Group].[id] = $1 AND [user_id] = $2
|
||||||
pub async fn can_edit_recipe_step(&self, user_id: i64, step_id: i64) -> Result<bool> {
|
pub async fn can_edit_recipe_step(&self, user_id: i64, step_id: i64) -> Result<bool> {
|
||||||
sqlx::query_scalar(
|
sqlx::query_scalar(
|
||||||
r#"
|
r#"
|
||||||
SELECT COUNT(*)
|
SELECT COUNT(*) = 1
|
||||||
FROM [Recipe]
|
FROM [Recipe]
|
||||||
INNER JOIN [Group] ON [Group].[recipe_id] = [Recipe].[id]
|
INNER JOIN [Group] ON [Group].[recipe_id] = [Recipe].[id]
|
||||||
INNER JOIN [Step] ON [Step].[group_id] = [Group].[id]
|
INNER JOIN [Step] ON [Step].[group_id] = [Group].[id]
|
||||||
|
|
@ -171,6 +173,16 @@ WHERE [Recipe].[user_id] = $1
|
||||||
.map_err(DBError::from)
|
.map_err(DBError::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn set_recipe_servings(&self, recipe_id: i64, servings: Option<u32>) -> Result<()> {
|
||||||
|
sqlx::query("UPDATE [Recipe] SET [servings] = $2 WHERE [id] = $1")
|
||||||
|
.bind(recipe_id)
|
||||||
|
.bind(servings)
|
||||||
|
.execute(&self.pool)
|
||||||
|
.await
|
||||||
|
.map(|_| ())
|
||||||
|
.map_err(DBError::from)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn set_recipe_estimated_time(
|
pub async fn set_recipe_estimated_time(
|
||||||
&self,
|
&self,
|
||||||
recipe_id: i64,
|
recipe_id: i64,
|
||||||
|
|
@ -222,6 +234,15 @@ WHERE [Recipe].[user_id] = $1
|
||||||
.map_err(DBError::from)
|
.map_err(DBError::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn rm_recipe(&self, recipe_id: i64) -> Result<()> {
|
||||||
|
sqlx::query("DELETE FROM [Recipe] WHERE [id] = $1")
|
||||||
|
.bind(recipe_id)
|
||||||
|
.execute(&self.pool)
|
||||||
|
.await
|
||||||
|
.map(|_| ())
|
||||||
|
.map_err(DBError::from)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_groups(&self, recipe_id: i64) -> Result<Vec<model::Group>> {
|
pub async fn get_groups(&self, recipe_id: i64) -> Result<Vec<model::Group>> {
|
||||||
let mut tx = self.tx().await?;
|
let mut tx = self.tx().await?;
|
||||||
let mut groups: Vec<model::Group> = sqlx::query_as(
|
let mut groups: Vec<model::Group> = sqlx::query_as(
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ pub struct Recipe {
|
||||||
#[sqlx(try_from = "u32")]
|
#[sqlx(try_from = "u32")]
|
||||||
pub difficulty: Difficulty,
|
pub difficulty: Difficulty,
|
||||||
|
|
||||||
pub servings: u32,
|
pub servings: Option<u32>,
|
||||||
pub is_published: bool,
|
pub is_published: bool,
|
||||||
// pub tags: Vec<String>,
|
// pub tags: Vec<String>,
|
||||||
// pub groups: Vec<Group>,
|
// pub groups: Vec<Group>,
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,7 @@ async fn main() {
|
||||||
"/recipe/set_description",
|
"/recipe/set_description",
|
||||||
put(services::ron::set_recipe_description),
|
put(services::ron::set_recipe_description),
|
||||||
)
|
)
|
||||||
|
.route("/recipe/set_servings", put(services::ron::set_servings))
|
||||||
.route(
|
.route(
|
||||||
"/recipe/set_estimated_time",
|
"/recipe/set_estimated_time",
|
||||||
put(services::ron::set_estimated_time),
|
put(services::ron::set_estimated_time),
|
||||||
|
|
@ -101,6 +102,7 @@ async fn main() {
|
||||||
"/recipe/set_is_published",
|
"/recipe/set_is_published",
|
||||||
put(services::ron::set_is_published),
|
put(services::ron::set_is_published),
|
||||||
)
|
)
|
||||||
|
.route("/recipe/remove", delete(services::ron::rm))
|
||||||
.route("/recipe/get_groups", get(services::ron::get_groups))
|
.route("/recipe/get_groups", get(services::ron::get_groups))
|
||||||
.route("/recipe/add_group", post(services::ron::add_group))
|
.route("/recipe/add_group", post(services::ron::add_group))
|
||||||
.route("/recipe/remove_group", delete(services::ron::rm_group))
|
.route("/recipe/remove_group", delete(services::ron::rm_group))
|
||||||
|
|
|
||||||
|
|
@ -33,25 +33,28 @@ pub async fn edit_recipe(
|
||||||
Path(recipe_id): Path<i64>,
|
Path(recipe_id): Path<i64>,
|
||||||
) -> Result<Response> {
|
) -> Result<Response> {
|
||||||
if let Some(user) = user {
|
if let Some(user) = user {
|
||||||
let recipe = connection.get_recipe(recipe_id).await?.unwrap();
|
if let Some(recipe) = connection.get_recipe(recipe_id).await? {
|
||||||
if recipe.user_id == user.id {
|
if recipe.user_id == user.id {
|
||||||
let recipes = Recipes {
|
let recipes = Recipes {
|
||||||
published: connection.get_all_published_recipe_titles().await?,
|
published: connection.get_all_published_recipe_titles().await?,
|
||||||
unpublished: connection
|
unpublished: connection
|
||||||
.get_all_unpublished_recipe_titles(user.id)
|
.get_all_unpublished_recipe_titles(user.id)
|
||||||
.await?,
|
.await?,
|
||||||
current_id: Some(recipe_id),
|
current_id: Some(recipe_id),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(RecipeEditTemplate {
|
Ok(RecipeEditTemplate {
|
||||||
user: Some(user),
|
user: Some(user),
|
||||||
recipes,
|
recipes,
|
||||||
recipe,
|
recipe,
|
||||||
languages: consts::LANGUAGES,
|
languages: consts::LANGUAGES,
|
||||||
|
}
|
||||||
|
.into_response())
|
||||||
|
} else {
|
||||||
|
Ok(MessageTemplate::new("Not allowed to edit this recipe").into_response())
|
||||||
}
|
}
|
||||||
.into_response())
|
|
||||||
} else {
|
} else {
|
||||||
Ok(MessageTemplate::new("Not allowed to edit this recipe").into_response())
|
Ok(MessageTemplate::new("Recipe not found").into_response())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Ok(MessageTemplate::new("Not logged in").into_response())
|
Ok(MessageTemplate::new("Not logged in").into_response())
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ use crate::{
|
||||||
ron_utils::{ron_error, ron_response},
|
ron_utils::{ron_error, ron_response},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const NOT_AUTHORIZED_MESSAGE: &str = "Action not authorized";
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
pub async fn update_user(
|
pub async fn update_user(
|
||||||
|
|
@ -33,7 +35,7 @@ pub async fn update_user(
|
||||||
} else {
|
} else {
|
||||||
return Err(ErrorResponse::from(ron_error(
|
return Err(ErrorResponse::from(ron_error(
|
||||||
StatusCode::UNAUTHORIZED,
|
StatusCode::UNAUTHORIZED,
|
||||||
"Action not authorized",
|
NOT_AUTHORIZED_MESSAGE,
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
Ok(StatusCode::OK)
|
Ok(StatusCode::OK)
|
||||||
|
|
@ -51,7 +53,7 @@ async fn check_user_rights_recipe(
|
||||||
{
|
{
|
||||||
Err(ErrorResponse::from(ron_error(
|
Err(ErrorResponse::from(ron_error(
|
||||||
StatusCode::UNAUTHORIZED,
|
StatusCode::UNAUTHORIZED,
|
||||||
"Action not authorized",
|
NOT_AUTHORIZED_MESSAGE,
|
||||||
)))
|
)))
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -70,7 +72,7 @@ async fn check_user_rights_recipe_group(
|
||||||
{
|
{
|
||||||
Err(ErrorResponse::from(ron_error(
|
Err(ErrorResponse::from(ron_error(
|
||||||
StatusCode::UNAUTHORIZED,
|
StatusCode::UNAUTHORIZED,
|
||||||
"Action not authorized",
|
NOT_AUTHORIZED_MESSAGE,
|
||||||
)))
|
)))
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -89,7 +91,7 @@ async fn check_user_rights_recipe_step(
|
||||||
{
|
{
|
||||||
Err(ErrorResponse::from(ron_error(
|
Err(ErrorResponse::from(ron_error(
|
||||||
StatusCode::UNAUTHORIZED,
|
StatusCode::UNAUTHORIZED,
|
||||||
"Action not authorized",
|
NOT_AUTHORIZED_MESSAGE,
|
||||||
)))
|
)))
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -108,7 +110,7 @@ async fn check_user_rights_recipe_ingredient(
|
||||||
{
|
{
|
||||||
Err(ErrorResponse::from(ron_error(
|
Err(ErrorResponse::from(ron_error(
|
||||||
StatusCode::UNAUTHORIZED,
|
StatusCode::UNAUTHORIZED,
|
||||||
"Action not authorized",
|
NOT_AUTHORIZED_MESSAGE,
|
||||||
)))
|
)))
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -141,6 +143,19 @@ pub async fn set_recipe_description(
|
||||||
Ok(StatusCode::OK)
|
Ok(StatusCode::OK)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[debug_handler]
|
||||||
|
pub async fn set_servings(
|
||||||
|
State(connection): State<db::Connection>,
|
||||||
|
Extension(user): Extension<Option<model::User>>,
|
||||||
|
ExtractRon(ron): ExtractRon<common::ron_api::SetRecipeServings>,
|
||||||
|
) -> Result<StatusCode> {
|
||||||
|
check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
|
||||||
|
connection
|
||||||
|
.set_recipe_servings(ron.recipe_id, ron.servings)
|
||||||
|
.await?;
|
||||||
|
Ok(StatusCode::OK)
|
||||||
|
}
|
||||||
|
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
pub async fn set_estimated_time(
|
pub async fn set_estimated_time(
|
||||||
State(connection): State<db::Connection>,
|
State(connection): State<db::Connection>,
|
||||||
|
|
@ -193,6 +208,17 @@ pub async fn set_is_published(
|
||||||
Ok(StatusCode::OK)
|
Ok(StatusCode::OK)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[debug_handler]
|
||||||
|
pub async fn rm(
|
||||||
|
State(connection): State<db::Connection>,
|
||||||
|
Extension(user): Extension<Option<model::User>>,
|
||||||
|
ExtractRon(ron): ExtractRon<common::ron_api::Remove>,
|
||||||
|
) -> Result<impl IntoResponse> {
|
||||||
|
check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
|
||||||
|
connection.rm_recipe(ron.recipe_id).await?;
|
||||||
|
Ok(StatusCode::OK)
|
||||||
|
}
|
||||||
|
|
||||||
impl From<model::Group> for common::ron_api::Group {
|
impl From<model::Group> for common::ron_api::Group {
|
||||||
fn from(group: model::Group) -> Self {
|
fn from(group: model::Group) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,19 @@
|
||||||
<textarea
|
<textarea
|
||||||
id="text-area-description">{{ recipe.description }}</textarea>
|
id="text-area-description">{{ recipe.description }}</textarea>
|
||||||
|
|
||||||
|
<label for="input-servings">Servings</label>
|
||||||
|
<input
|
||||||
|
id="input-servings"
|
||||||
|
type="number"
|
||||||
|
step="1" min="1" max="100"
|
||||||
|
value="
|
||||||
|
{% match recipe.servings %}
|
||||||
|
{% when Some with (s) %}
|
||||||
|
{{ s }}
|
||||||
|
{% when None %}
|
||||||
|
|
||||||
|
{% endmatch %}"/>
|
||||||
|
|
||||||
<label for="input-estimated-time">Estimated time [min]</label>
|
<label for="input-estimated-time">Estimated time [min]</label>
|
||||||
<input
|
<input
|
||||||
id="input-estimated-time"
|
id="input-estimated-time"
|
||||||
|
|
@ -30,7 +43,7 @@
|
||||||
{% when Some with (t) %}
|
{% when Some with (t) %}
|
||||||
{{ t }}
|
{{ t }}
|
||||||
{% when None %}
|
{% when None %}
|
||||||
0
|
|
||||||
{% endmatch %}"/>
|
{% endmatch %}"/>
|
||||||
|
|
||||||
<label for="select-difficulty">Difficulty</label>
|
<label for="select-difficulty">Difficulty</label>
|
||||||
|
|
@ -61,6 +74,8 @@
|
||||||
>
|
>
|
||||||
<label for="input-is-published">Is published</label>
|
<label for="input-is-published">Is published</label>
|
||||||
|
|
||||||
|
<input id="input-delete" type="button" value="Delete recipe" />
|
||||||
|
|
||||||
<div id="groups-container">
|
<div id="groups-container">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,12 @@ pub struct SetRecipeDescription {
|
||||||
pub description: String,
|
pub description: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
|
pub struct SetRecipeServings {
|
||||||
|
pub recipe_id: i64,
|
||||||
|
pub servings: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct SetRecipeEstimatedTime {
|
pub struct SetRecipeEstimatedTime {
|
||||||
pub recipe_id: i64,
|
pub recipe_id: i64,
|
||||||
|
|
@ -65,6 +71,11 @@ pub struct SetIsPublished {
|
||||||
pub is_published: bool,
|
pub is_published: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
|
pub struct Remove {
|
||||||
|
pub recipe_id: i64,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct AddRecipeGroup {
|
pub struct AddRecipeGroup {
|
||||||
pub recipe_id: i64,
|
pub recipe_id: i64,
|
||||||
|
|
|
||||||
|
|
@ -54,116 +54,165 @@ pub fn recipe_edit(recipe_id: i64) -> Result<(), JsValue> {
|
||||||
{
|
{
|
||||||
let description: HtmlTextAreaElement = by_id("text-area-description");
|
let description: HtmlTextAreaElement = by_id("text-area-description");
|
||||||
let mut current_description = description.value();
|
let mut current_description = description.value();
|
||||||
let on_input_description_blur =
|
|
||||||
EventListener::new(&description.clone(), "blur", move |_event| {
|
EventListener::new(&description.clone(), "blur", move |_event| {
|
||||||
if description.value() != current_description {
|
if description.value() != current_description {
|
||||||
current_description = description.value();
|
current_description = description.value();
|
||||||
let body = ron_api::SetRecipeDescription {
|
let body = ron_api::SetRecipeDescription {
|
||||||
recipe_id,
|
recipe_id,
|
||||||
description: description.value(),
|
description: description.value(),
|
||||||
};
|
};
|
||||||
spawn_local(async move {
|
spawn_local(async move {
|
||||||
let _ = request::put::<(), _>("recipe/set_description", body).await;
|
let _ = request::put::<(), _>("recipe/set_description", body).await;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
on_input_description_blur.forget();
|
.forget();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Servings.
|
||||||
|
{
|
||||||
|
let servings: HtmlInputElement = by_id("input-servings");
|
||||||
|
let mut current_servings = servings.value_as_number();
|
||||||
|
EventListener::new(&servings.clone(), "input", move |_event| {
|
||||||
|
let n = servings.value_as_number();
|
||||||
|
if n.is_nan() {
|
||||||
|
servings.set_value("");
|
||||||
|
}
|
||||||
|
if n != current_servings {
|
||||||
|
let servings = if n.is_nan() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
// TODO: Find a better way to validate integer numbers.
|
||||||
|
let n = n as u32;
|
||||||
|
servings.set_value_as_number(n as f64);
|
||||||
|
Some(n)
|
||||||
|
};
|
||||||
|
current_servings = n;
|
||||||
|
let body = ron_api::SetRecipeServings {
|
||||||
|
recipe_id,
|
||||||
|
servings,
|
||||||
|
};
|
||||||
|
spawn_local(async move {
|
||||||
|
let _ = request::put::<(), _>("recipe/set_servings", body).await;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.forget();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Estimated time.
|
// Estimated time.
|
||||||
{
|
{
|
||||||
let estimated_time: HtmlInputElement = by_id("input-estimated-time");
|
let estimated_time: HtmlInputElement = by_id("input-estimated-time");
|
||||||
let mut current_time = estimated_time.value_as_number();
|
let mut current_time = estimated_time.value_as_number();
|
||||||
let on_input_estimated_time_blur =
|
|
||||||
EventListener::new(&estimated_time.clone(), "blur", move |_event| {
|
EventListener::new(&estimated_time.clone(), "input", move |_event| {
|
||||||
let n = estimated_time.value_as_number();
|
let n = estimated_time.value_as_number();
|
||||||
if n.is_nan() {
|
if n.is_nan() {
|
||||||
estimated_time.set_value("");
|
estimated_time.set_value("");
|
||||||
}
|
}
|
||||||
if n != current_time {
|
if n != current_time {
|
||||||
let time = if n.is_nan() {
|
let time = if n.is_nan() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
// TODO: Find a better way to validate integer numbers.
|
// TODO: Find a better way to validate integer numbers.
|
||||||
let n = n as u32;
|
let n = n as u32;
|
||||||
estimated_time.set_value_as_number(n as f64);
|
estimated_time.set_value_as_number(n as f64);
|
||||||
Some(n)
|
Some(n)
|
||||||
};
|
};
|
||||||
current_time = n;
|
current_time = n;
|
||||||
let body = ron_api::SetRecipeEstimatedTime {
|
let body = ron_api::SetRecipeEstimatedTime {
|
||||||
recipe_id,
|
recipe_id,
|
||||||
estimated_time: time,
|
estimated_time: time,
|
||||||
};
|
};
|
||||||
spawn_local(async move {
|
spawn_local(async move {
|
||||||
let _ = request::put::<(), _>("recipe/set_estimated_time", body).await;
|
let _ = request::put::<(), _>("recipe/set_estimated_time", body).await;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
on_input_estimated_time_blur.forget();
|
.forget();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Difficulty.
|
// Difficulty.
|
||||||
{
|
{
|
||||||
let difficulty: HtmlSelectElement = by_id("select-difficulty");
|
let difficulty: HtmlSelectElement = by_id("select-difficulty");
|
||||||
let mut current_difficulty = difficulty.value();
|
let mut current_difficulty = difficulty.value();
|
||||||
let on_select_difficulty_blur =
|
|
||||||
EventListener::new(&difficulty.clone(), "blur", move |_event| {
|
|
||||||
if difficulty.value() != current_difficulty {
|
|
||||||
current_difficulty = difficulty.value();
|
|
||||||
|
|
||||||
let body = ron_api::SetRecipeDifficulty {
|
EventListener::new(&difficulty.clone(), "blur", move |_event| {
|
||||||
recipe_id,
|
if difficulty.value() != current_difficulty {
|
||||||
difficulty: ron_api::Difficulty::try_from(
|
current_difficulty = difficulty.value();
|
||||||
current_difficulty.parse::<u32>().unwrap(),
|
|
||||||
)
|
let body = ron_api::SetRecipeDifficulty {
|
||||||
.unwrap(),
|
recipe_id,
|
||||||
};
|
difficulty: ron_api::Difficulty::try_from(
|
||||||
spawn_local(async move {
|
current_difficulty.parse::<u32>().unwrap(),
|
||||||
let _ = request::put::<(), _>("recipe/set_difficulty", body).await;
|
)
|
||||||
});
|
.unwrap(),
|
||||||
}
|
};
|
||||||
});
|
spawn_local(async move {
|
||||||
on_select_difficulty_blur.forget();
|
let _ = request::put::<(), _>("recipe/set_difficulty", body).await;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.forget();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Language.
|
// Language.
|
||||||
{
|
{
|
||||||
let language: HtmlSelectElement = by_id("select-language");
|
let language: HtmlSelectElement = by_id("select-language");
|
||||||
let mut current_language = language.value();
|
let mut current_language = language.value();
|
||||||
let on_select_language_blur =
|
EventListener::new(&language.clone(), "blur", move |_event| {
|
||||||
EventListener::new(&language.clone(), "blur", move |_event| {
|
if language.value() != current_language {
|
||||||
if language.value() != current_language {
|
current_language = language.value();
|
||||||
current_language = language.value();
|
|
||||||
|
|
||||||
let body = ron_api::SetRecipeLanguage {
|
let body = ron_api::SetRecipeLanguage {
|
||||||
recipe_id,
|
recipe_id,
|
||||||
lang: language.value(),
|
lang: language.value(),
|
||||||
};
|
};
|
||||||
spawn_local(async move {
|
spawn_local(async move {
|
||||||
let _ = request::put::<(), _>("recipe/set_language", body).await;
|
let _ = request::put::<(), _>("recipe/set_language", body).await;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
on_select_language_blur.forget();
|
.forget();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is published.
|
// Is published.
|
||||||
{
|
{
|
||||||
let is_published: HtmlInputElement = by_id("input-is-published");
|
let is_published: HtmlInputElement = by_id("input-is-published");
|
||||||
let on_input_is_published_blur =
|
EventListener::new(&is_published.clone(), "input", move |_event| {
|
||||||
EventListener::new(&is_published.clone(), "input", move |_event| {
|
let body = ron_api::SetIsPublished {
|
||||||
let body = ron_api::SetIsPublished {
|
recipe_id,
|
||||||
recipe_id,
|
is_published: is_published.checked(),
|
||||||
is_published: is_published.checked(),
|
};
|
||||||
};
|
spawn_local(async move {
|
||||||
spawn_local(async move {
|
let _ = request::put::<(), _>("recipe/set_is_published", body).await;
|
||||||
let _ = request::put::<(), _>("recipe/set_is_published", body).await;
|
reload_recipes_list(recipe_id).await;
|
||||||
reload_recipes_list(recipe_id).await;
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
on_input_is_published_blur.forget();
|
})
|
||||||
|
.forget();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete recipe button.
|
||||||
|
let delete_button: HtmlInputElement = by_id("input-delete");
|
||||||
|
EventListener::new(&delete_button, "click", move |_event| {
|
||||||
|
let title: HtmlInputElement = by_id("input-title");
|
||||||
|
spawn_local(async move {
|
||||||
|
if modal_dialog::show(&format!(
|
||||||
|
"Are you sure to delete the recipe '{}'",
|
||||||
|
title.value()
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
let body = ron_api::Remove { recipe_id };
|
||||||
|
let _ = request::delete::<(), _>("recipe/remove", body).await;
|
||||||
|
|
||||||
|
// by_id::<Element>(&format!("group-{}", group_id)).remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.forget();
|
||||||
|
|
||||||
fn create_group_element(group: &ron_api::Group) -> Element {
|
fn create_group_element(group: &ron_api::Group) -> Element {
|
||||||
let group_id = group.id;
|
let group_id = group.id;
|
||||||
let group_element: Element = select_and_clone("#hidden-templates .group");
|
let group_element: Element = select_and_clone("#hidden-templates .group");
|
||||||
|
|
@ -374,7 +423,7 @@ pub fn recipe_edit(recipe_id: i64) -> Result<(), JsValue> {
|
||||||
.map_or("".to_string(), |q| q.to_string()),
|
.map_or("".to_string(), |q| q.to_string()),
|
||||||
);
|
);
|
||||||
let mut current_quantity = quantity.value_as_number();
|
let mut current_quantity = quantity.value_as_number();
|
||||||
EventListener::new(&quantity.clone(), "blur", move |_event| {
|
EventListener::new(&quantity.clone(), "input", move |_event| {
|
||||||
let n = quantity.value_as_number();
|
let n = quantity.value_as_number();
|
||||||
if n.is_nan() {
|
if n.is_nan() {
|
||||||
quantity.set_value("");
|
quantity.set_value("");
|
||||||
|
|
@ -479,60 +528,3 @@ pub fn recipe_edit(recipe_id: i64) -> Result<(), JsValue> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub fn user_edit(doc: Document) -> Result<(), JsValue> {
|
|
||||||
// log!("user_edit");
|
|
||||||
|
|
||||||
// let button = doc
|
|
||||||
// .query_selector("#user-edit input[type='button']")?
|
|
||||||
// .unwrap();
|
|
||||||
|
|
||||||
// let on_click_submit = EventListener::new(&button, "click", move |_event| {
|
|
||||||
// log!("Click!");
|
|
||||||
|
|
||||||
// let input_name = doc.get_element_by_id("input-name").unwrap();
|
|
||||||
// let name = input_name.dyn_ref::<HtmlInputElement>().unwrap().value();
|
|
||||||
|
|
||||||
// let update_data = common::ron_api::UpdateProfile {
|
|
||||||
// name: Some(name),
|
|
||||||
// email: None,
|
|
||||||
// password: None,
|
|
||||||
// };
|
|
||||||
|
|
||||||
// let body = common::ron_api::to_string(update_data);
|
|
||||||
|
|
||||||
// let doc = doc.clone();
|
|
||||||
// spawn_local(async move {
|
|
||||||
// match Request::put("/ron-api/user/update")
|
|
||||||
// .header("Content-Type", "application/ron")
|
|
||||||
// .body(body)
|
|
||||||
// .unwrap()
|
|
||||||
// .send()
|
|
||||||
// .await
|
|
||||||
// {
|
|
||||||
// Ok(resp) => {
|
|
||||||
// log!("Status code: {}", resp.status());
|
|
||||||
// if resp.status() == 200 {
|
|
||||||
// toast::show(Level::Info, "Profile saved");
|
|
||||||
// } else {
|
|
||||||
// toast::show(
|
|
||||||
// Level::Error,
|
|
||||||
// &format!(
|
|
||||||
// "Status code: {} {}",
|
|
||||||
// resp.status(),
|
|
||||||
// resp.text().await.unwrap()
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// Err(error) => {
|
|
||||||
// toast::show(Level::Info, &format!("Internal server error: {}", error));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
|
|
||||||
// on_click_submit.forget();
|
|
||||||
|
|
||||||
// Ok(())
|
|
||||||
// }
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue