Recipe edit (WIP): add API to set some recipe values
This commit is contained in:
parent
c6dfff065c
commit
dd05a673d9
20 changed files with 690 additions and 2189 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
|
@ -2622,9 +2622,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.90"
|
version = "2.0.91"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
|
checksum = "d53cbcb5a243bd33b7858b1d7f4aca2153490815872d86d955d6ea29f743c035"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
|
||||||
1951
backend/Cargo.lock
generated
1951
backend/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -79,7 +79,7 @@ body {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
||||||
// .recipes-list {
|
// #recipes-list {
|
||||||
// text-align: left;
|
// text-align: left;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,14 +18,14 @@ VALUES (
|
||||||
NULL
|
NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
INSERT INTO [Recipe] ([user_id], [title])
|
INSERT INTO [Recipe] ([user_id], [title], [is_published])
|
||||||
VALUES (1, 'Croissant au jambon');
|
VALUES (1, 'Croissant au jambon', true);
|
||||||
|
|
||||||
INSERT INTO [Recipe] ([user_id], [title])
|
INSERT INTO [Recipe] ([user_id], [title], [is_published])
|
||||||
VALUES (1, 'Gratin de thon aux olives');
|
VALUES (1, 'Gratin de thon aux olives', true);
|
||||||
|
|
||||||
INSERT INTO [Recipe] ([user_id], [title])
|
INSERT INTO [Recipe] ([user_id], [title], [is_published])
|
||||||
VALUES (1, 'Saumon en croute');
|
VALUES (1, 'Saumon en croute', true);
|
||||||
|
|
||||||
INSERT INTO [Recipe] ([user_id], [title])
|
INSERT INTO [Recipe] ([user_id], [title], [is_published])
|
||||||
VALUES (2, 'Ouiche lorraine');
|
VALUES (2, 'Ouiche lorraine', true);
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,46 @@
|
||||||
use super::{Connection, DBError, Result};
|
use super::{Connection, DBError, Result};
|
||||||
use crate::{
|
use crate::{consts, data::model};
|
||||||
consts,
|
|
||||||
data::model::{self, Difficulty},
|
use common::ron_api::Difficulty;
|
||||||
};
|
|
||||||
|
|
||||||
impl Connection {
|
impl Connection {
|
||||||
pub async fn get_all_recipe_titles(&self) -> Result<Vec<(i64, String)>> {
|
pub async fn get_all_published_recipe_titles(&self) -> Result<Vec<(i64, String)>> {
|
||||||
sqlx::query_as("SELECT [id], [title] FROM [Recipe] ORDER BY [title]")
|
sqlx::query_as(
|
||||||
.fetch_all(&self.pool)
|
r#"
|
||||||
|
SELECT [id], [title]
|
||||||
|
FROM [Recipe]
|
||||||
|
WHERE [is_published] = true
|
||||||
|
ORDER BY [title]
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.fetch_all(&self.pool)
|
||||||
|
.await
|
||||||
|
.map_err(DBError::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_all_unpublished_recipe_titles(
|
||||||
|
&self,
|
||||||
|
owned_by: i64,
|
||||||
|
) -> Result<Vec<(i64, String)>> {
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
SELECT [id], [title]
|
||||||
|
FROM [Recipe]
|
||||||
|
WHERE [is_published] = false AND [user_id] = $1
|
||||||
|
ORDER BY [title]
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(owned_by)
|
||||||
|
.fetch_all(&self.pool)
|
||||||
|
.await
|
||||||
|
.map_err(DBError::from)
|
||||||
|
}
|
||||||
|
|
||||||
|
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"#)
|
||||||
|
.bind(recipe_id)
|
||||||
|
.bind(user_id)
|
||||||
|
.fetch_one(&self.pool)
|
||||||
.await
|
.await
|
||||||
.map_err(DBError::from)
|
.map_err(DBError::from)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use chrono::prelude::*;
|
use chrono::prelude::*;
|
||||||
|
use common::ron_api::Difficulty;
|
||||||
use sqlx::{self, FromRow};
|
use sqlx::{self, FromRow};
|
||||||
|
|
||||||
#[derive(Debug, Clone, FromRow)]
|
#[derive(Debug, Clone, FromRow)]
|
||||||
|
|
@ -50,29 +51,3 @@ pub struct Ingredient {
|
||||||
pub quantity: i32,
|
pub quantity: i32,
|
||||||
pub quantity_unit: String,
|
pub quantity_unit: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
|
||||||
pub enum Difficulty {
|
|
||||||
Unknown = 0,
|
|
||||||
Easy = 1,
|
|
||||||
Medium = 2,
|
|
||||||
Hard = 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<u32> for Difficulty {
|
|
||||||
type Error = &'static str;
|
|
||||||
fn try_from(value: u32) -> Result<Self, Self::Error> {
|
|
||||||
Ok(match value {
|
|
||||||
1 => Self::Easy,
|
|
||||||
2 => Self::Medium,
|
|
||||||
3 => Self::Hard,
|
|
||||||
_ => Self::Unknown,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Difficulty> for u32 {
|
|
||||||
fn from(value: Difficulty) -> Self {
|
|
||||||
value as u32
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,17 @@ use askama::Template;
|
||||||
use crate::data::model;
|
use crate::data::model;
|
||||||
|
|
||||||
pub struct Recipes {
|
pub struct Recipes {
|
||||||
pub list: Vec<(i64, String)>,
|
pub published: Vec<(i64, String)>,
|
||||||
|
pub unpublished: Vec<(i64, String)>,
|
||||||
pub current_id: Option<i64>,
|
pub current_id: Option<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Recipes {
|
||||||
|
pub fn is_current(&self, id: &&i64) -> bool {
|
||||||
|
self.current_id == Some(**id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "home.html")]
|
#[template(path = "home.html")]
|
||||||
pub struct HomeTemplate {
|
pub struct HomeTemplate {
|
||||||
|
|
@ -111,3 +118,10 @@ pub struct RecipeEditTemplate {
|
||||||
pub recipe: model::Recipe,
|
pub recipe: model::Recipe,
|
||||||
pub languages: [(&'static str, &'static str); 2],
|
pub languages: [(&'static str, &'static str); 2],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "recipes_list_fragment.html")]
|
||||||
|
pub struct RecipesListFragmentTemplate {
|
||||||
|
pub user: Option<model::User>,
|
||||||
|
pub recipes: Recipes,
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use axum::{
|
||||||
http::StatusCode,
|
http::StatusCode,
|
||||||
middleware::{self, Next},
|
middleware::{self, Next},
|
||||||
response::{Response, Result},
|
response::{Response, Result},
|
||||||
routing::get,
|
routing::{get, put},
|
||||||
Router,
|
Router,
|
||||||
};
|
};
|
||||||
use axum_extra::extract::cookie::CookieJar;
|
use axum_extra::extract::cookie::CookieJar;
|
||||||
|
|
@ -86,8 +86,28 @@ async fn main() {
|
||||||
let ron_api_routes = Router::new()
|
let ron_api_routes = Router::new()
|
||||||
// Disabled: update user profile is now made with a post data ('edit_user_post').
|
// Disabled: update user profile is now made with a post data ('edit_user_post').
|
||||||
// .route("/user/update", put(services::ron::update_user))
|
// .route("/user/update", put(services::ron::update_user))
|
||||||
|
.route("/recipe/set_title", put(services::ron::set_recipe_title))
|
||||||
|
.route(
|
||||||
|
"/recipe/set_description",
|
||||||
|
put(services::ron::set_recipe_description),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/recipe/set_estimated_time",
|
||||||
|
put(services::ron::set_estimated_time),
|
||||||
|
)
|
||||||
|
.route("/recipe/set_difficulty", put(services::ron::set_difficulty))
|
||||||
|
.route("/recipe/set_language", put(services::ron::set_language))
|
||||||
|
.route(
|
||||||
|
"/recipe/set_is_published",
|
||||||
|
put(services::ron::set_is_published),
|
||||||
|
)
|
||||||
.fallback(services::ron::not_found);
|
.fallback(services::ron::not_found);
|
||||||
|
|
||||||
|
let fragments_routes = Router::new().route(
|
||||||
|
"/recipes_list",
|
||||||
|
get(services::fragments::recipes_list_fragments),
|
||||||
|
);
|
||||||
|
|
||||||
let html_routes = Router::new()
|
let html_routes = Router::new()
|
||||||
.route("/", get(services::home_page))
|
.route("/", get(services::home_page))
|
||||||
.route(
|
.route(
|
||||||
|
|
@ -123,6 +143,7 @@ async fn main() {
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.merge(html_routes)
|
.merge(html_routes)
|
||||||
|
.nest("/fragments", fragments_routes)
|
||||||
.nest("/ron-api", ron_api_routes)
|
.nest("/ron-api", ron_api_routes)
|
||||||
.fallback(services::not_found)
|
.fallback(services::not_found)
|
||||||
.layer(TraceLayer::new_for_http())
|
.layer(TraceLayer::new_for_http())
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ use crate::{
|
||||||
ron_utils,
|
ron_utils,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub mod fragments;
|
||||||
pub mod recipe;
|
pub mod recipe;
|
||||||
pub mod ron;
|
pub mod ron;
|
||||||
pub mod user;
|
pub mod user;
|
||||||
|
|
@ -46,15 +47,19 @@ pub async fn home_page(
|
||||||
State(connection): State<db::Connection>,
|
State(connection): State<db::Connection>,
|
||||||
Extension(user): Extension<Option<model::User>>,
|
Extension(user): Extension<Option<model::User>>,
|
||||||
) -> Result<impl IntoResponse> {
|
) -> Result<impl IntoResponse> {
|
||||||
let recipes = connection.get_all_recipe_titles().await?;
|
let recipes = Recipes {
|
||||||
|
published: connection.get_all_published_recipe_titles().await?,
|
||||||
Ok(HomeTemplate {
|
unpublished: if let Some(user) = user.as_ref() {
|
||||||
user,
|
connection
|
||||||
recipes: Recipes {
|
.get_all_unpublished_recipe_titles(user.id)
|
||||||
list: recipes,
|
.await?
|
||||||
current_id: None,
|
} else {
|
||||||
|
vec![]
|
||||||
},
|
},
|
||||||
})
|
current_id: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(HomeTemplate { user, recipes })
|
||||||
}
|
}
|
||||||
|
|
||||||
///// 404 /////
|
///// 404 /////
|
||||||
|
|
|
||||||
31
backend/src/services/fragments.rs
Normal file
31
backend/src/services/fragments.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
use axum::{
|
||||||
|
debug_handler,
|
||||||
|
extract::{Extension, State},
|
||||||
|
response::{IntoResponse, Result},
|
||||||
|
};
|
||||||
|
// use tracing::{event, Level};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
data::{db, model},
|
||||||
|
html_templates::*,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[debug_handler]
|
||||||
|
pub async fn recipes_list_fragments(
|
||||||
|
State(connection): State<db::Connection>,
|
||||||
|
Extension(user): Extension<Option<model::User>>,
|
||||||
|
) -> Result<impl IntoResponse> {
|
||||||
|
let recipes = Recipes {
|
||||||
|
published: connection.get_all_published_recipe_titles().await?,
|
||||||
|
unpublished: if let Some(user) = user.as_ref() {
|
||||||
|
connection
|
||||||
|
.get_all_unpublished_recipe_titles(user.id)
|
||||||
|
.await?
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
},
|
||||||
|
current_id: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(RecipesListFragmentTemplate { user, recipes })
|
||||||
|
}
|
||||||
|
|
@ -35,18 +35,23 @@ pub async fn edit_recipe(
|
||||||
if let Some(user) = user {
|
if let Some(user) = user {
|
||||||
let recipe = connection.get_recipe(recipe_id).await?.unwrap();
|
let recipe = connection.get_recipe(recipe_id).await?.unwrap();
|
||||||
if recipe.user_id == user.id {
|
if recipe.user_id == user.id {
|
||||||
|
let recipes = Recipes {
|
||||||
|
published: connection.get_all_published_recipe_titles().await?,
|
||||||
|
unpublished: connection
|
||||||
|
.get_all_unpublished_recipe_titles(user.id)
|
||||||
|
.await?,
|
||||||
|
current_id: Some(recipe_id),
|
||||||
|
};
|
||||||
|
|
||||||
Ok(RecipeEditTemplate {
|
Ok(RecipeEditTemplate {
|
||||||
user: Some(user),
|
user: Some(user),
|
||||||
recipes: Recipes {
|
recipes,
|
||||||
list: connection.get_all_recipe_titles().await?,
|
|
||||||
current_id: Some(recipe_id),
|
|
||||||
},
|
|
||||||
recipe,
|
recipe,
|
||||||
languages: consts::LANGUAGES,
|
languages: consts::LANGUAGES,
|
||||||
}
|
}
|
||||||
.into_response())
|
.into_response())
|
||||||
} else {
|
} else {
|
||||||
Ok(MessageTemplate::new("Unable to edit this recipe").into_response())
|
Ok(MessageTemplate::new("Not allowed to edit this recipe").into_response())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Ok(MessageTemplate::new("Not logged in").into_response())
|
Ok(MessageTemplate::new("Not logged in").into_response())
|
||||||
|
|
@ -59,17 +64,37 @@ pub async fn view(
|
||||||
Extension(user): Extension<Option<model::User>>,
|
Extension(user): Extension<Option<model::User>>,
|
||||||
Path(recipe_id): Path<i64>,
|
Path(recipe_id): Path<i64>,
|
||||||
) -> Result<Response> {
|
) -> Result<Response> {
|
||||||
let recipes = connection.get_all_recipe_titles().await?;
|
|
||||||
match connection.get_recipe(recipe_id).await? {
|
match connection.get_recipe(recipe_id).await? {
|
||||||
Some(recipe) => Ok(RecipeViewTemplate {
|
Some(recipe) => {
|
||||||
user,
|
if !recipe.is_published
|
||||||
recipes: Recipes {
|
&& (user.is_none() || recipe.user_id != user.as_ref().unwrap().id)
|
||||||
list: recipes,
|
{
|
||||||
current_id: Some(recipe.id),
|
return Ok(MessageTemplate::new_with_user(
|
||||||
},
|
&format!("Not allowed the view the recipe {}", recipe_id),
|
||||||
recipe,
|
user,
|
||||||
|
)
|
||||||
|
.into_response());
|
||||||
|
}
|
||||||
|
|
||||||
|
let recipes = Recipes {
|
||||||
|
published: connection.get_all_published_recipe_titles().await?,
|
||||||
|
unpublished: if let Some(user) = user.as_ref() {
|
||||||
|
connection
|
||||||
|
.get_all_unpublished_recipe_titles(user.id)
|
||||||
|
.await?
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
},
|
||||||
|
current_id: Some(recipe_id),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(RecipeViewTemplate {
|
||||||
|
user,
|
||||||
|
recipes,
|
||||||
|
recipe,
|
||||||
|
}
|
||||||
|
.into_response())
|
||||||
}
|
}
|
||||||
.into_response()),
|
|
||||||
None => Ok(MessageTemplate::new_with_user(
|
None => Ok(MessageTemplate::new_with_user(
|
||||||
&format!("Cannot find the recipe {}", recipe_id),
|
&format!("Cannot find the recipe {}", recipe_id),
|
||||||
user,
|
user,
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,103 @@ pub async fn update_user(
|
||||||
Ok(StatusCode::OK)
|
Ok(StatusCode::OK)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn check_user_rights(
|
||||||
|
connection: &db::Connection,
|
||||||
|
user: &Option<model::User>,
|
||||||
|
recipe_id: i64,
|
||||||
|
) -> Result<()> {
|
||||||
|
if user.is_none()
|
||||||
|
|| !connection
|
||||||
|
.can_edit_recipe(user.as_ref().unwrap().id, recipe_id)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Err(ErrorResponse::from(ron_error(
|
||||||
|
StatusCode::UNAUTHORIZED,
|
||||||
|
"Action not authorized",
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[debug_handler]
|
||||||
|
pub async fn set_recipe_title(
|
||||||
|
State(connection): State<db::Connection>,
|
||||||
|
Extension(user): Extension<Option<model::User>>,
|
||||||
|
ExtractRon(ron): ExtractRon<common::ron_api::SetRecipeTitle>,
|
||||||
|
) -> Result<StatusCode> {
|
||||||
|
check_user_rights(&connection, &user, ron.recipe_id).await?;
|
||||||
|
connection
|
||||||
|
.set_recipe_title(ron.recipe_id, &ron.title)
|
||||||
|
.await?;
|
||||||
|
Ok(StatusCode::OK)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[debug_handler]
|
||||||
|
pub async fn set_recipe_description(
|
||||||
|
State(connection): State<db::Connection>,
|
||||||
|
Extension(user): Extension<Option<model::User>>,
|
||||||
|
ExtractRon(ron): ExtractRon<common::ron_api::SetRecipeDescription>,
|
||||||
|
) -> Result<StatusCode> {
|
||||||
|
check_user_rights(&connection, &user, ron.recipe_id).await?;
|
||||||
|
connection
|
||||||
|
.set_recipe_description(ron.recipe_id, &ron.description)
|
||||||
|
.await?;
|
||||||
|
Ok(StatusCode::OK)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[debug_handler]
|
||||||
|
pub async fn set_estimated_time(
|
||||||
|
State(connection): State<db::Connection>,
|
||||||
|
Extension(user): Extension<Option<model::User>>,
|
||||||
|
ExtractRon(ron): ExtractRon<common::ron_api::SetRecipeEstimatedTime>,
|
||||||
|
) -> Result<StatusCode> {
|
||||||
|
check_user_rights(&connection, &user, ron.recipe_id).await?;
|
||||||
|
connection
|
||||||
|
.set_recipe_estimated_time(ron.recipe_id, ron.estimated_time)
|
||||||
|
.await?;
|
||||||
|
Ok(StatusCode::OK)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[debug_handler]
|
||||||
|
pub async fn set_difficulty(
|
||||||
|
State(connection): State<db::Connection>,
|
||||||
|
Extension(user): Extension<Option<model::User>>,
|
||||||
|
ExtractRon(ron): ExtractRon<common::ron_api::SetRecipeDifficulty>,
|
||||||
|
) -> Result<StatusCode> {
|
||||||
|
check_user_rights(&connection, &user, ron.recipe_id).await?;
|
||||||
|
connection
|
||||||
|
.set_recipe_difficulty(ron.recipe_id, ron.difficulty)
|
||||||
|
.await?;
|
||||||
|
Ok(StatusCode::OK)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[debug_handler]
|
||||||
|
pub async fn set_language(
|
||||||
|
State(connection): State<db::Connection>,
|
||||||
|
Extension(user): Extension<Option<model::User>>,
|
||||||
|
ExtractRon(ron): ExtractRon<common::ron_api::SetRecipeLanguage>,
|
||||||
|
) -> Result<StatusCode> {
|
||||||
|
check_user_rights(&connection, &user, ron.recipe_id).await?;
|
||||||
|
connection
|
||||||
|
.set_recipe_language(ron.recipe_id, &ron.lang)
|
||||||
|
.await?;
|
||||||
|
Ok(StatusCode::OK)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[debug_handler]
|
||||||
|
pub async fn set_is_published(
|
||||||
|
State(connection): State<db::Connection>,
|
||||||
|
Extension(user): Extension<Option<model::User>>,
|
||||||
|
ExtractRon(ron): ExtractRon<common::ron_api::SetIsPublished>,
|
||||||
|
) -> Result<StatusCode> {
|
||||||
|
check_user_rights(&connection, &user, ron.recipe_id).await?;
|
||||||
|
connection
|
||||||
|
.set_recipe_is_published(ron.recipe_id, ron.is_published)
|
||||||
|
.await?;
|
||||||
|
Ok(StatusCode::OK)
|
||||||
|
}
|
||||||
|
|
||||||
///// 404 /////
|
///// 404 /////
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
pub async fn not_found(Extension(_user): Extension<Option<model::User>>) -> impl IntoResponse {
|
pub async fn not_found(Extension(_user): Extension<Option<model::User>>) -> impl IntoResponse {
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,6 @@
|
||||||
{% extends "base_with_header.html" %}
|
{% extends "base_with_header.html" %}
|
||||||
|
|
||||||
{% macro recipe_item(id, title, class) %}
|
|
||||||
<a href="/recipe/view/{{ id }}" class="{{ class }}">
|
|
||||||
{% if title == "" %}
|
|
||||||
{# TODO: Translation #}
|
|
||||||
No title defined
|
|
||||||
{% else %}
|
|
||||||
{{ title }}
|
|
||||||
{% endif %}
|
|
||||||
</a>
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% block main_container %}
|
{% block main_container %}
|
||||||
<nav class="recipes-list">
|
{% include "recipes_list_fragment.html" %}
|
||||||
<ul>
|
|
||||||
{% for (id, title) in recipes.list %}
|
|
||||||
<li>
|
|
||||||
{% match recipes.current_id %}
|
|
||||||
{# Don't know how to avoid
|
|
||||||
repetition: comparing (using '==' or .eq()) recipes.current_recipe_id.unwrap() and id doesn't work.
|
|
||||||
Guards for match don't exist.
|
|
||||||
See: https://github.com/djc/askama/issues/752 #}
|
|
||||||
{% when Some(current_id) %}
|
|
||||||
{% if current_id == id %}
|
|
||||||
{% call recipe_item(id, title, "recipe-item-current") %}
|
|
||||||
{% else %}
|
|
||||||
{% call recipe_item(id, title, "recipe-item") %}
|
|
||||||
{% endif %}
|
|
||||||
{% when None %}
|
|
||||||
{% call recipe_item(id, title, "recipe-item") %}
|
|
||||||
{% endmatch %}
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -42,10 +42,10 @@
|
||||||
|
|
||||||
<label for="select-difficulty">Difficulty</label>
|
<label for="select-difficulty">Difficulty</label>
|
||||||
<select id="select-difficulty" name="difficulty">
|
<select id="select-difficulty" name="difficulty">
|
||||||
<option value="0" {%+ call is_difficulty(crate::data::model::Difficulty::Unknown) %}> - </option>
|
<option value="0" {%+ call is_difficulty(common::ron_api::Difficulty::Unknown) %}> - </option>
|
||||||
<option value="1" {%+ call is_difficulty(crate::data::model::Difficulty::Easy) %}>Easy</option>
|
<option value="1" {%+ call is_difficulty(common::ron_api::Difficulty::Easy) %}>Easy</option>
|
||||||
<option value="2" {%+ call is_difficulty(crate::data::model::Difficulty::Medium) %}>Medium</option>
|
<option value="2" {%+ call is_difficulty(common::ron_api::Difficulty::Medium) %}>Medium</option>
|
||||||
<option value="3" {%+ call is_difficulty(crate::data::model::Difficulty::Hard) %}>Hard</option>
|
<option value="3" {%+ call is_difficulty(common::ron_api::Difficulty::Hard) %}>Hard</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<label for="select-language">Language</label>
|
<label for="select-language">Language</label>
|
||||||
|
|
@ -59,7 +59,10 @@
|
||||||
id="input-is-published"
|
id="input-is-published"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
name="is-published"
|
name="is-published"
|
||||||
value="{{ recipe.is_published }}" />
|
{%+ if recipe.is_published %}
|
||||||
|
checked
|
||||||
|
{% endif %}
|
||||||
|
>
|
||||||
<label for="input-is-published">Is published</label>
|
<label for="input-is-published">Is published</label>
|
||||||
|
|
||||||
<div id="groups-container">
|
<div id="groups-container">
|
||||||
|
|
|
||||||
50
backend/templates/recipes_list_fragment.html
Normal file
50
backend/templates/recipes_list_fragment.html
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
{% macro recipe_item(id, title, class) %}
|
||||||
|
<a href="/recipe/view/{{ id }}" class="{{ class }}" id="recipe-{{ id }}">
|
||||||
|
{% if title == "" %}
|
||||||
|
{# TODO: Translation #}
|
||||||
|
Untitled recipe
|
||||||
|
{% else %}
|
||||||
|
{{ title }}
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
|
||||||
|
<div id="recipes-list">
|
||||||
|
|
||||||
|
{% if !recipes.unpublished.is_empty() %}
|
||||||
|
Unpublished recipes
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<nav class="recipes-list-unpublished">
|
||||||
|
<ul>
|
||||||
|
{% for (id, title) in recipes.unpublished %}
|
||||||
|
<li>
|
||||||
|
{% if recipes.is_current(id) %}
|
||||||
|
{% call recipe_item(id, title, "recipe-item-current") %}
|
||||||
|
{% else %}
|
||||||
|
{% call recipe_item(id, title, "recipe-item") %}
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{% if !recipes.unpublished.is_empty() %}
|
||||||
|
<hr />
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<nav class="recipes-list-published">
|
||||||
|
<ul>
|
||||||
|
{% for (id, title) in recipes.published %}
|
||||||
|
<li>
|
||||||
|
{% if recipes.is_current(id) %}
|
||||||
|
{% call recipe_item(id, title, "recipe-item-current") %}
|
||||||
|
{% else %}
|
||||||
|
{% call recipe_item(id, title, "recipe-item") %}
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
@ -16,84 +16,134 @@ pub struct SetRecipeDescription {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct AddRecipeImage {
|
pub struct SetRecipeEstimatedTime {
|
||||||
pub recipe_id: i64,
|
pub recipe_id: i64,
|
||||||
pub image: Vec<u8>,
|
pub estimated_time: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
|
||||||
|
pub enum Difficulty {
|
||||||
|
Unknown = 0,
|
||||||
|
Easy = 1,
|
||||||
|
Medium = 2,
|
||||||
|
Hard = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u32> for Difficulty {
|
||||||
|
type Error = &'static str;
|
||||||
|
fn try_from(value: u32) -> Result<Self, Self::Error> {
|
||||||
|
Ok(match value {
|
||||||
|
1 => Self::Easy,
|
||||||
|
2 => Self::Medium,
|
||||||
|
3 => Self::Hard,
|
||||||
|
_ => Self::Unknown,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Difficulty> for u32 {
|
||||||
|
fn from(value: Difficulty) -> Self {
|
||||||
|
value as u32
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct AddRecipeImageReply {
|
pub struct SetRecipeDifficulty {
|
||||||
pub image_id: i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
|
||||||
pub struct RemoveRecipeImage {
|
|
||||||
pub image_id: i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
|
||||||
pub struct AddRecipeIngredient {
|
|
||||||
pub group_id: i64,
|
|
||||||
pub name: String,
|
|
||||||
pub quantity_value: Option<f64>,
|
|
||||||
pub quantity_unit: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
|
||||||
pub struct AddRecipeIngredientReply {
|
|
||||||
pub ingredient_id: i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
|
||||||
pub struct RemoveRecipeIngredient {
|
|
||||||
pub group_id: i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
|
||||||
pub struct SetRecipeIngredientsOrder {
|
|
||||||
pub group_id: i64,
|
|
||||||
pub ingredient_ids: Vec<i64>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
|
||||||
pub struct AddRecipeGroup {
|
|
||||||
pub recipe_id: i64,
|
pub recipe_id: i64,
|
||||||
pub name: String,
|
pub difficulty: Difficulty,
|
||||||
pub quantity_value: Option<f64>,
|
|
||||||
pub quantity_unit: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct AddRecipeGroupReply {
|
pub struct SetRecipeLanguage {
|
||||||
pub group_id: i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
|
||||||
pub struct RemoveRecipeGroupReply {
|
|
||||||
pub group_id: i64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
|
||||||
pub struct SetRecipeGroupsOrder {
|
|
||||||
pub recipe_id: i64,
|
pub recipe_id: i64,
|
||||||
pub group_ids: Vec<i64>,
|
pub lang: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct AddRecipeStep {
|
pub struct SetIsPublished {
|
||||||
pub group_id: i64,
|
pub recipe_id: i64,
|
||||||
pub name: String,
|
pub is_published: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
// #[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct AddRecipeStepReply {
|
// pub struct AddRecipeImage {
|
||||||
pub step_id: i64,
|
// pub recipe_id: i64,
|
||||||
}
|
// pub image: Vec<u8>,
|
||||||
|
// }
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
// #[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct RemoveRecipeStep {
|
// pub struct AddRecipeImageReply {
|
||||||
pub step_id: i64,
|
// pub image_id: i64,
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
// #[derive(Serialize, Deserialize, Clone)]
|
||||||
|
// pub struct RemoveRecipeImage {
|
||||||
|
// pub image_id: i64,
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[derive(Serialize, Deserialize, Clone)]
|
||||||
|
// pub struct AddRecipeIngredient {
|
||||||
|
// pub group_id: i64,
|
||||||
|
// pub name: String,
|
||||||
|
// pub quantity_value: Option<f64>,
|
||||||
|
// pub quantity_unit: String,
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[derive(Serialize, Deserialize, Clone)]
|
||||||
|
// pub struct AddRecipeIngredientReply {
|
||||||
|
// pub ingredient_id: i64,
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[derive(Serialize, Deserialize, Clone)]
|
||||||
|
// pub struct RemoveRecipeIngredient {
|
||||||
|
// pub group_id: i64,
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[derive(Serialize, Deserialize, Clone)]
|
||||||
|
// pub struct SetRecipeIngredientsOrder {
|
||||||
|
// pub group_id: i64,
|
||||||
|
// pub ingredient_ids: Vec<i64>,
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[derive(Serialize, Deserialize, Clone)]
|
||||||
|
// pub struct AddRecipeGroup {
|
||||||
|
// pub recipe_id: i64,
|
||||||
|
// pub name: String,
|
||||||
|
// pub quantity_value: Option<f64>,
|
||||||
|
// pub quantity_unit: String,
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[derive(Serialize, Deserialize, Clone)]
|
||||||
|
// pub struct AddRecipeGroupReply {
|
||||||
|
// pub group_id: i64,
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[derive(Serialize, Deserialize, Clone)]
|
||||||
|
// pub struct RemoveRecipeGroupReply {
|
||||||
|
// pub group_id: i64,
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[derive(Serialize, Deserialize, Clone)]
|
||||||
|
// pub struct SetRecipeGroupsOrder {
|
||||||
|
// pub recipe_id: i64,
|
||||||
|
// pub group_ids: Vec<i64>,
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[derive(Serialize, Deserialize, Clone)]
|
||||||
|
// pub struct AddRecipeStep {
|
||||||
|
// pub group_id: i64,
|
||||||
|
// pub name: String,
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[derive(Serialize, Deserialize, Clone)]
|
||||||
|
// pub struct AddRecipeStepReply {
|
||||||
|
// pub step_id: i64,
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[derive(Serialize, Deserialize, Clone)]
|
||||||
|
// pub struct RemoveRecipeStep {
|
||||||
|
// pub step_id: i64,
|
||||||
|
// }
|
||||||
|
|
||||||
///// PROFILE /////
|
///// PROFILE /////
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ web-sys = { version = "0.3", features = [
|
||||||
"EventTarget",
|
"EventTarget",
|
||||||
"HtmlLabelElement",
|
"HtmlLabelElement",
|
||||||
"HtmlInputElement",
|
"HtmlInputElement",
|
||||||
|
"HtmlSelectElement",
|
||||||
] }
|
] }
|
||||||
|
|
||||||
gloo = "0.11"
|
gloo = "0.11"
|
||||||
|
|
|
||||||
|
|
@ -1,73 +1,254 @@
|
||||||
use gloo::{console::log, events::EventListener, net::http::Request};
|
use gloo::{console::log, events::EventListener, net::http::Request, utils::document};
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
use wasm_bindgen_futures::spawn_local;
|
use wasm_bindgen_futures::spawn_local;
|
||||||
use web_sys::{Document, HtmlInputElement};
|
use web_sys::{Document, HtmlInputElement, HtmlSelectElement};
|
||||||
|
|
||||||
use crate::toast::{self, Level};
|
use crate::toast::{self, Level};
|
||||||
|
|
||||||
pub fn recipe_edit(doc: Document) -> Result<(), JsValue> {
|
async fn api_request(body: String, api_name: &str) {
|
||||||
let title_input = doc.get_element_by_id("title_field").unwrap();
|
if let Err(error) = Request::put(&format!("/ron-api/recipe/{}", api_name))
|
||||||
Ok(())
|
.header("Content-Type", "application/ron")
|
||||||
|
.body(body)
|
||||||
|
.unwrap()
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
toast::show(Level::Info, &format!("Internal server error: {}", error));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn user_edit(doc: Document) -> Result<(), JsValue> {
|
async fn reload_recipes_list() {
|
||||||
log!("user_edit");
|
match Request::get("/fragments/recipes_list").send().await {
|
||||||
|
Err(error) => {
|
||||||
|
toast::show(Level::Info, &format!("Internal server error: {}", error));
|
||||||
|
}
|
||||||
|
Ok(response) => {
|
||||||
|
let list = document().get_element_by_id("recipes-list").unwrap();
|
||||||
|
list.set_outer_html(&response.text().await.unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let button = doc
|
pub fn recipe_edit(recipe_id: i64) -> Result<(), JsValue> {
|
||||||
.query_selector("#user-edit input[type='button']")?
|
// Title.
|
||||||
.unwrap();
|
{
|
||||||
|
let input_title = document().get_element_by_id("input-title").unwrap();
|
||||||
let on_click_submit = EventListener::new(&button, "click", move |_event| {
|
let mut current_title = input_title.dyn_ref::<HtmlInputElement>().unwrap().value();
|
||||||
log!("Click!");
|
let on_input_title_blur = EventListener::new(&input_title, "blur", move |_event| {
|
||||||
|
let input_title = document().get_element_by_id("input-title").unwrap();
|
||||||
let input_name = doc.get_element_by_id("input-name").unwrap();
|
let title = input_title.dyn_ref::<HtmlInputElement>().unwrap();
|
||||||
let name = input_name.dyn_ref::<HtmlInputElement>().unwrap().value();
|
if title.value() != current_title {
|
||||||
|
current_title = title.value();
|
||||||
let update_data = common::ron_api::UpdateProfile {
|
let body = common::ron_api::to_string(common::ron_api::SetRecipeTitle {
|
||||||
name: Some(name),
|
recipe_id,
|
||||||
email: None,
|
title: title.value(),
|
||||||
password: None,
|
});
|
||||||
};
|
spawn_local(async move {
|
||||||
|
api_request(body, "set_title").await;
|
||||||
let body = common::ron_api::to_string(update_data);
|
reload_recipes_list().await;
|
||||||
|
});
|
||||||
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", doc);
|
|
||||||
} else {
|
|
||||||
toast::show(
|
|
||||||
Level::Error,
|
|
||||||
&format!(
|
|
||||||
"Status code: {} {}",
|
|
||||||
resp.status(),
|
|
||||||
resp.text().await.unwrap()
|
|
||||||
),
|
|
||||||
doc,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(error) => {
|
|
||||||
toast::show(
|
|
||||||
Level::Info,
|
|
||||||
&format!("Internal server error: {}", error),
|
|
||||||
doc,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
on_input_title_blur.forget();
|
||||||
|
}
|
||||||
|
|
||||||
on_click_submit.forget();
|
// Description.
|
||||||
|
{
|
||||||
|
let input_description = document().get_element_by_id("input-description").unwrap();
|
||||||
|
let mut current_description = input_description
|
||||||
|
.dyn_ref::<HtmlInputElement>()
|
||||||
|
.unwrap()
|
||||||
|
.value();
|
||||||
|
let on_input_description_blur =
|
||||||
|
EventListener::new(&input_description, "blur", move |_event| {
|
||||||
|
let input_description = document().get_element_by_id("input-description").unwrap();
|
||||||
|
let description = input_description.dyn_ref::<HtmlInputElement>().unwrap();
|
||||||
|
if description.value() != current_description {
|
||||||
|
current_description = description.value();
|
||||||
|
let body = common::ron_api::to_string(common::ron_api::SetRecipeDescription {
|
||||||
|
recipe_id,
|
||||||
|
description: description.value(),
|
||||||
|
});
|
||||||
|
spawn_local(async move {
|
||||||
|
api_request(body, "set_description").await;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
on_input_description_blur.forget();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Estimated time.
|
||||||
|
{
|
||||||
|
let input_estimated_time = document()
|
||||||
|
.get_element_by_id("input-estimated-time")
|
||||||
|
.unwrap();
|
||||||
|
let mut current_time = input_estimated_time
|
||||||
|
.dyn_ref::<HtmlInputElement>()
|
||||||
|
.unwrap()
|
||||||
|
.value();
|
||||||
|
let on_input_estimated_time_blur =
|
||||||
|
EventListener::new(&input_estimated_time, "blur", move |_event| {
|
||||||
|
let input_estimated_time = document()
|
||||||
|
.get_element_by_id("input-estimated-time")
|
||||||
|
.unwrap();
|
||||||
|
let estimated_time = input_estimated_time.dyn_ref::<HtmlInputElement>().unwrap();
|
||||||
|
if estimated_time.value() != current_time {
|
||||||
|
let time = if estimated_time.value().is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
if let Ok(t) = estimated_time.value().parse::<u32>() {
|
||||||
|
Some(t)
|
||||||
|
} else {
|
||||||
|
estimated_time.set_value(¤t_time);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
current_time = estimated_time.value();
|
||||||
|
let body =
|
||||||
|
common::ron_api::to_string(common::ron_api::SetRecipeEstimatedTime {
|
||||||
|
recipe_id,
|
||||||
|
estimated_time: time,
|
||||||
|
});
|
||||||
|
spawn_local(async move {
|
||||||
|
api_request(body, "set_estimated_time").await;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
on_input_estimated_time_blur.forget();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Difficulty.
|
||||||
|
{
|
||||||
|
let select_difficulty = document().get_element_by_id("select-difficulty").unwrap();
|
||||||
|
let mut current_difficulty = select_difficulty
|
||||||
|
.dyn_ref::<HtmlSelectElement>()
|
||||||
|
.unwrap()
|
||||||
|
.value();
|
||||||
|
let on_select_difficulty_blur =
|
||||||
|
EventListener::new(&select_difficulty, "blur", move |_event| {
|
||||||
|
let select_difficulty = document().get_element_by_id("select-difficulty").unwrap();
|
||||||
|
let difficulty = select_difficulty.dyn_ref::<HtmlSelectElement>().unwrap();
|
||||||
|
if difficulty.value() != current_difficulty {
|
||||||
|
current_difficulty = difficulty.value();
|
||||||
|
|
||||||
|
let body = common::ron_api::to_string(common::ron_api::SetRecipeDifficulty {
|
||||||
|
recipe_id,
|
||||||
|
difficulty: common::ron_api::Difficulty::try_from(
|
||||||
|
current_difficulty.parse::<u32>().unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
});
|
||||||
|
spawn_local(async move {
|
||||||
|
api_request(body, "set_difficulty").await;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
on_select_difficulty_blur.forget();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Language.
|
||||||
|
{
|
||||||
|
let select_language = document().get_element_by_id("select-language").unwrap();
|
||||||
|
let mut current_language = select_language
|
||||||
|
.dyn_ref::<HtmlSelectElement>()
|
||||||
|
.unwrap()
|
||||||
|
.value();
|
||||||
|
let on_select_language_blur = EventListener::new(&select_language, "blur", move |_event| {
|
||||||
|
let select_language = document().get_element_by_id("select-language").unwrap();
|
||||||
|
let difficulty = select_language.dyn_ref::<HtmlSelectElement>().unwrap();
|
||||||
|
if difficulty.value() != current_language {
|
||||||
|
current_language = difficulty.value();
|
||||||
|
|
||||||
|
let body = common::ron_api::to_string(common::ron_api::SetRecipeLanguage {
|
||||||
|
recipe_id,
|
||||||
|
lang: difficulty.value(),
|
||||||
|
});
|
||||||
|
spawn_local(async move {
|
||||||
|
api_request(body, "set_language").await;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
on_select_language_blur.forget();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is published.
|
||||||
|
{
|
||||||
|
let input_is_published = document().get_element_by_id("input-is-published").unwrap();
|
||||||
|
let on_input_is_published_blur =
|
||||||
|
EventListener::new(&input_is_published, "input", move |_event| {
|
||||||
|
let input_is_published =
|
||||||
|
document().get_element_by_id("input-is-published").unwrap();
|
||||||
|
let is_published = input_is_published.dyn_ref::<HtmlInputElement>().unwrap();
|
||||||
|
|
||||||
|
let body = common::ron_api::to_string(common::ron_api::SetIsPublished {
|
||||||
|
recipe_id,
|
||||||
|
is_published: is_published.checked(),
|
||||||
|
});
|
||||||
|
spawn_local(async move {
|
||||||
|
api_request(body, "set_is_published").await;
|
||||||
|
reload_recipes_list().await;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
on_input_is_published_blur.forget();
|
||||||
|
}
|
||||||
|
|
||||||
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(())
|
||||||
|
// }
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ mod handles;
|
||||||
mod toast;
|
mod toast;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
use gloo::{console::log, events::EventListener};
|
use gloo::{console::log, events::EventListener, utils::window};
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
use web_sys::console;
|
use web_sys::console;
|
||||||
|
|
||||||
|
|
@ -21,18 +21,16 @@ use web_sys::console;
|
||||||
pub fn main() -> Result<(), JsValue> {
|
pub fn main() -> Result<(), JsValue> {
|
||||||
console_error_panic_hook::set_once();
|
console_error_panic_hook::set_once();
|
||||||
|
|
||||||
let window = web_sys::window().expect("no global `window` exists");
|
// let window = web_sys::window().expect("no global `window` exists");
|
||||||
let document = window.document().expect("should have a document on window");
|
// let document = window.document().expect("should have a document on window");
|
||||||
|
|
||||||
let location = window.location().pathname()?;
|
let location = window().location().pathname()?;
|
||||||
let path: Vec<&str> = location.split('/').skip(1).collect();
|
let path: Vec<&str> = location.split('/').skip(1).collect();
|
||||||
|
|
||||||
match path[..] {
|
match path[..] {
|
||||||
["recipe", "edit", id] => {
|
["recipe", "edit", id] => {
|
||||||
let id = id.parse::<i64>().unwrap(); // TODO: remove unwrap.
|
let id = id.parse::<i64>().unwrap(); // TODO: remove unwrap.
|
||||||
log!("recipe edit ID: {}", id);
|
handles::recipe_edit(id)?;
|
||||||
|
|
||||||
handles::recipe_edit(document)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable: user editing data are now submitted as classic form data.
|
// Disable: user editing data are now submitted as classic form data.
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use gloo::{console::log, timers::callback::Timeout};
|
use gloo::{console::log, timers::callback::Timeout, utils::document};
|
||||||
use web_sys::{console, Document, HtmlInputElement};
|
use web_sys::{console, Document, HtmlInputElement};
|
||||||
|
|
||||||
pub enum Level {
|
pub enum Level {
|
||||||
|
|
@ -8,8 +8,8 @@ pub enum Level {
|
||||||
Warning,
|
Warning,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show(level: Level, message: &str, doc: Document) {
|
pub fn show(level: Level, message: &str) {
|
||||||
let toast_element = doc.get_element_by_id("toast").unwrap();
|
let toast_element = document().get_element_by_id("toast").unwrap();
|
||||||
toast_element.set_inner_html(message);
|
toast_element.set_inner_html(message);
|
||||||
toast_element.set_class_name("show");
|
toast_element.set_class_name("show");
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue