From f2e0aa3b43c3b4328a98c93d3786b0f6b6dfbcf5 Mon Sep 17 00:00:00 2001 From: Greg Burri Date: Tue, 15 Apr 2025 11:34:18 +0200 Subject: [PATCH] Form CSS style + dev panel (WIP) --- backend/scss/main.scss | 45 ++++++++++++++++++++++- backend/src/consts.rs | 2 + backend/src/html_templates.rs | 6 +++ backend/src/main.rs | 1 + backend/src/ron_utils.rs | 4 +- backend/src/services/mod.rs | 36 +++++++++++++----- backend/src/services/ron/calendar.rs | 5 +-- backend/src/services/ron/mod.rs | 2 - backend/src/services/ron/rights.rs | 18 ++++----- backend/src/services/ron/shopping_list.rs | 4 +- backend/templates/base_with_header.html | 5 ++- backend/templates/dev_panel.html | 17 +++++++++ backend/templates/recipe_view.html | 4 +- frontend/src/lib.rs | 1 + frontend/src/modal_dialog.rs | 8 +++- frontend/src/pages/dev_panel.rs | 26 +++++++++++++ frontend/src/pages/mod.rs | 1 + frontend/src/pages/recipe_edit.rs | 3 +- 18 files changed, 154 insertions(+), 34 deletions(-) create mode 100644 backend/templates/dev_panel.html create mode 100644 frontend/src/pages/dev_panel.rs diff --git a/backend/scss/main.scss b/backend/scss/main.scss index c890dc3..40d40d6 100644 --- a/backend/scss/main.scss +++ b/backend/scss/main.scss @@ -266,13 +266,54 @@ body { // width: fit-content; // justify-self: flex-end; // } - // } + //} } } +.tag { + border: 0.1em solid consts.$color-3; + border-radius: 0.5em; + background-color: consts.$color-1; + margin: consts.$margin; + padding: consts.$margin; +} + +textarea, +input { + margin: consts.$margin; + padding: calc(consts.$margin / 2) calc(2 * consts.$margin); + + background-color: consts.$color-1; + border: solid 1px consts.$color-3; + color: consts.$text-color; + + &:focus { + outline: none; + background-color: consts.$color-2; + box-shadow: 0 0 5px color.adjust(consts.$color-3, $lightness: -20%); + } +} + +select { + margin: consts.$margin; + padding: calc(consts.$margin / 2) calc(2 * consts.$margin); + + background-color: consts.$color-1; + border: solid 1px consts.$color-3; + color: consts.$text-color; + + option { + color: inherit; + background-color: consts.$color-1; + } +} + +input[type="button"], +input[type="submit"], .button { margin: consts.$margin; padding: calc(consts.$margin / 2) calc(2 * consts.$margin); + border: 0.1em solid consts.$color-3; border-radius: 0.5em; background-color: consts.$color-2; @@ -281,6 +322,8 @@ body { 5px 5px 4px 3px color.adjust(consts.$color-2, $lightness: 4%) inset; cursor: pointer; + color: consts.$link-color; + &:hover { color: consts.$link-hover-color; } diff --git a/backend/src/consts.rs b/backend/src/consts.rs index f178c62..c6749e3 100644 --- a/backend/src/consts.rs +++ b/backend/src/consts.rs @@ -56,3 +56,5 @@ pub const REVERSE_PROXY_IP_HTTP_FIELD: &str = "x-real-ip"; // To avoid database lock. pub const MAX_DB_CONNECTION: u32 = 1; + +pub const NOT_AUTHORIZED_MESSAGE: &str = "Action not authorized"; diff --git a/backend/src/html_templates.rs b/backend/src/html_templates.rs index f946d0c..922d776 100644 --- a/backend/src/html_templates.rs +++ b/backend/src/html_templates.rs @@ -50,7 +50,13 @@ mod filters { #[template(path = "home.html")] pub struct HomeTemplate { pub context: Context, + pub recipes: Recipes, +} +#[derive(Template)] +#[template(path = "dev_panel.html")] +pub struct DevPanelTemplate { + pub context: Context, pub recipes: Recipes, } diff --git a/backend/src/main.rs b/backend/src/main.rs index 6742aa9..82496d9 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -261,6 +261,7 @@ async fn main() { let html_routes = Router::new() .route("/", get(services::home_page)) + .route("/dev_panel", get(services::dev_panel)) .route("/signup", get(services::user::sign_up_get)) .route("/validation", get(services::user::sign_up_validation)) .route("/revalidation", get(services::user::email_revalidation)) diff --git a/backend/src/ron_utils.rs b/backend/src/ron_utils.rs index 98dd03e..40403a4 100644 --- a/backend/src/ron_utils.rs +++ b/backend/src/ron_utils.rs @@ -1,11 +1,11 @@ use axum::{ body::Bytes, - http::{header, HeaderValue, StatusCode}, + http::{HeaderValue, StatusCode, header}, response::{IntoResponse, Response}, }; use common::ron_api; use ron::de::from_bytes; -use serde::{de::DeserializeOwned, Serialize}; +use serde::{Serialize, de::DeserializeOwned}; pub const RON_CONTENT_TYPE: HeaderValue = HeaderValue::from_static("application/ron"); diff --git a/backend/src/services/mod.rs b/backend/src/services/mod.rs index 138d1f9..37122e5 100644 --- a/backend/src/services/mod.rs +++ b/backend/src/services/mod.rs @@ -7,7 +7,7 @@ use axum::{ response::{Html, IntoResponse, Response}, }; -use crate::{Context, Result, data::db, html_templates::*, ron_utils}; +use crate::{Context, Result, consts, data::db, html_templates::*, ron_utils}; pub mod fragments; pub mod recipe; @@ -66,15 +66,31 @@ pub async fn home_page( pub async fn dev_panel( State(connection): State, Extension(context): Extension, -) -> Result { - Ok(Html( - HomeTemplate { - recipes: Recipes::new(connection, &context.user, context.tr.current_lang_code()) - .await?, - context, - } - .render()?, - )) +) -> Result { + if context.user.is_some() && context.user.as_ref().unwrap().is_admin { + Ok(Html( + DevPanelTemplate { + recipes: Recipes::new(connection, &context.user, context.tr.current_lang_code()) + .await?, + context, + } + .render()?, + ) + .into_response()) + } else { + Ok(( + StatusCode::UNAUTHORIZED, + Html( + MessageTemplate::new_with_user( + consts::NOT_AUTHORIZED_MESSAGE, + context.tr, + context.user, + ) + .render()?, + ), + ) + .into_response()) + } } ///// 404 ///// diff --git a/backend/src/services/ron/calendar.rs b/backend/src/services/ron/calendar.rs index 6ab5f6b..ef7d042 100644 --- a/backend/src/services/ron/calendar.rs +++ b/backend/src/services/ron/calendar.rs @@ -5,10 +5,9 @@ use axum::{ response::{ErrorResponse, IntoResponse, Response, Result}, }; use axum_extra::extract::Query; -// use tracing::{event, Level}; use crate::{ - Context, + Context, consts, data::{self, db}, ron_extractor::ExtractRon, ron_utils::{ron_error, ron_response_ok}, @@ -31,7 +30,7 @@ pub async fn get_scheduled_recipes( } else { Err(ErrorResponse::from(ron_error( StatusCode::UNAUTHORIZED, - super::NOT_AUTHORIZED_MESSAGE, + consts::NOT_AUTHORIZED_MESSAGE, ))) } } diff --git a/backend/src/services/ron/mod.rs b/backend/src/services/ron/mod.rs index 87ac32c..dab2a05 100644 --- a/backend/src/services/ron/mod.rs +++ b/backend/src/services/ron/mod.rs @@ -14,8 +14,6 @@ pub mod recipe; mod rights; pub mod shopping_list; -const NOT_AUTHORIZED_MESSAGE: &str = "Action not authorized"; - #[debug_handler] pub async fn set_lang( State(connection): State, diff --git a/backend/src/services/ron/rights.rs b/backend/src/services/ron/rights.rs index 2554c80..229be88 100644 --- a/backend/src/services/ron/rights.rs +++ b/backend/src/services/ron/rights.rs @@ -3,7 +3,7 @@ use axum::{ response::{ErrorResponse, Result}, }; -use crate::{data::db, model, ron_utils::ron_error}; +use crate::{consts, data::db, model, ron_utils::ron_error}; pub async fn check_user_rights_recipe( connection: &db::Connection, @@ -17,7 +17,7 @@ pub async fn check_user_rights_recipe( { Err(ErrorResponse::from(ron_error( StatusCode::UNAUTHORIZED, - super::NOT_AUTHORIZED_MESSAGE, + consts::NOT_AUTHORIZED_MESSAGE, ))) } else { Ok(()) @@ -36,7 +36,7 @@ pub async fn check_user_rights_recipe_group( { Err(ErrorResponse::from(ron_error( StatusCode::UNAUTHORIZED, - super::NOT_AUTHORIZED_MESSAGE, + consts::NOT_AUTHORIZED_MESSAGE, ))) } else { Ok(()) @@ -55,7 +55,7 @@ pub async fn check_user_rights_recipe_groups( { Err(ErrorResponse::from(ron_error( StatusCode::UNAUTHORIZED, - super::NOT_AUTHORIZED_MESSAGE, + consts::NOT_AUTHORIZED_MESSAGE, ))) } else { Ok(()) @@ -74,7 +74,7 @@ pub async fn check_user_rights_recipe_step( { Err(ErrorResponse::from(ron_error( StatusCode::UNAUTHORIZED, - super::NOT_AUTHORIZED_MESSAGE, + consts::NOT_AUTHORIZED_MESSAGE, ))) } else { Ok(()) @@ -93,7 +93,7 @@ pub async fn check_user_rights_recipe_steps( { Err(ErrorResponse::from(ron_error( StatusCode::UNAUTHORIZED, - super::NOT_AUTHORIZED_MESSAGE, + consts::NOT_AUTHORIZED_MESSAGE, ))) } else { Ok(()) @@ -112,7 +112,7 @@ pub async fn check_user_rights_recipe_ingredient( { Err(ErrorResponse::from(ron_error( StatusCode::UNAUTHORIZED, - super::NOT_AUTHORIZED_MESSAGE, + consts::NOT_AUTHORIZED_MESSAGE, ))) } else { Ok(()) @@ -131,7 +131,7 @@ pub async fn check_user_rights_recipe_ingredients( { Err(ErrorResponse::from(ron_error( StatusCode::UNAUTHORIZED, - super::NOT_AUTHORIZED_MESSAGE, + consts::NOT_AUTHORIZED_MESSAGE, ))) } else { Ok(()) @@ -150,7 +150,7 @@ pub async fn check_user_rights_shopping_list_entry( { Err(ErrorResponse::from(ron_error( StatusCode::UNAUTHORIZED, - super::NOT_AUTHORIZED_MESSAGE, + consts::NOT_AUTHORIZED_MESSAGE, ))) } else { Ok(()) diff --git a/backend/src/services/ron/shopping_list.rs b/backend/src/services/ron/shopping_list.rs index 2438758..6d62469 100644 --- a/backend/src/services/ron/shopping_list.rs +++ b/backend/src/services/ron/shopping_list.rs @@ -7,7 +7,7 @@ use axum::{ use common::ron_api; use crate::{ - Context, + Context, consts, data::db, model, ron_extractor::ExtractRon, @@ -48,7 +48,7 @@ pub async fn get( } else { Err(ErrorResponse::from(ron_error( StatusCode::UNAUTHORIZED, - super::NOT_AUTHORIZED_MESSAGE, + consts::NOT_AUTHORIZED_MESSAGE, ))) } } diff --git a/backend/templates/base_with_header.html b/backend/templates/base_with_header.html index 7f142ff..b764123 100644 --- a/backend/templates/base_with_header.html +++ b/backend/templates/base_with_header.html @@ -8,7 +8,10 @@ {% match context.user %} {% when Some with (user) %} - {{ context.tr.t(Sentence::CreateNewRecipe) }} + {{ context.tr.t(Sentence::CreateNewRecipe) }} + {% if user.is_admin %} + Dev panel + {% endif %} {% if user.name == "" %} {{ user.email }} diff --git a/backend/templates/dev_panel.html b/backend/templates/dev_panel.html new file mode 100644 index 0000000..89a90d1 --- /dev/null +++ b/backend/templates/dev_panel.html @@ -0,0 +1,17 @@ +{% extends "base_with_list.html" %} + +{% block content %} + +
+ + +
+ +
+ + +
+ +{% endblock %} \ No newline at end of file diff --git a/backend/templates/recipe_view.html b/backend/templates/recipe_view.html index 01979e9..6d7c64b 100644 --- a/backend/templates/recipe_view.html +++ b/backend/templates/recipe_view.html @@ -7,11 +7,11 @@ {% if let Some(user) = context.user %} {% if crate::data::model::can_user_edit_recipe(user, recipe) %} -
Edit + Edit {% endif %} {% endif %} - {{ context.tr.t(Sentence::CalendarAddToPlanner) }} + {{ context.tr.t(Sentence::CalendarAddToPlanner) }}
{% for tag in recipe.tags %} diff --git a/frontend/src/lib.rs b/frontend/src/lib.rs index 4c6f71c..b67f618 100644 --- a/frontend/src/lib.rs +++ b/frontend/src/lib.rs @@ -53,6 +53,7 @@ pub fn main() -> Result<(), JsValue> { let id = id.parse::().unwrap(); // TODO: remove unwrap. pages::recipe_view::setup_page(id, is_user_logged) } + ["dev_panel"] => pages::dev_panel::setup_page(), // Home. [""] => pages::home::setup_page(is_user_logged), _ => log!("Path unknown: ", location), diff --git a/frontend/src/modal_dialog.rs b/frontend/src/modal_dialog.rs index 9095c9b..01c499a 100644 --- a/frontend/src/modal_dialog.rs +++ b/frontend/src/modal_dialog.rs @@ -3,15 +3,18 @@ use web_sys::{Element, HtmlDialogElement}; use crate::{ on_click, - utils::{by_id, selector_and_clone, SelectorExt}, + utils::{SelectorExt, by_id, selector_and_clone}, }; +/// Show a modal dialog with a copy of the given HTML element as content. pub async fn show(element_selector: &str) -> bool { show_and_initialize(element_selector, async |_| Some(())) .await .is_some() } +/// Show a modal dialog with a copy of the given HTML element as content and execute an initilizer +/// to modify the given content. pub async fn show_and_initialize(element_selector: &str, initializer: T) -> Option where T: AsyncFn(Element) -> U, @@ -19,6 +22,9 @@ where show_and_initialize_with_ok(element_selector, initializer, |_, result| result).await } +/// Show a modal dialog with a copy of the given HTML element as content and execute an initilizer +/// to modify the given content. +/// Call the given function when OK is pressed. pub async fn show_and_initialize_with_ok( element_selector: &str, initializer: T, diff --git a/frontend/src/pages/dev_panel.rs b/frontend/src/pages/dev_panel.rs new file mode 100644 index 0000000..e1fbab8 --- /dev/null +++ b/frontend/src/pages/dev_panel.rs @@ -0,0 +1,26 @@ +use gloo::{console::log, events::EventListener}; +use wasm_bindgen::prelude::*; +use wasm_bindgen_futures::spawn_local; +use web_sys::{Element, HtmlInputElement}; + +use crate::{ + calendar, modal_dialog, + recipe_scheduler::RecipeScheduler, + shopping_list::ShoppingList, + toast::{self, Level}, + utils::{SelectorExt, by_id, get_current_lang, get_locale, selector}, +}; + +pub fn setup_page() { + EventListener::new(&by_id("test-toast"), "click", move |_event| { + toast::show_message(Level::Info, "This is a message"); + }) + .forget(); + + EventListener::new(&by_id("test-modal-dialog"), "click", move |_event| { + spawn_local(async move { + modal_dialog::show("#hidden-templates").await; + }); + }) + .forget(); +} diff --git a/frontend/src/pages/mod.rs b/frontend/src/pages/mod.rs index 2db264d..64bf2e1 100644 --- a/frontend/src/pages/mod.rs +++ b/frontend/src/pages/mod.rs @@ -1,3 +1,4 @@ +pub mod dev_panel; pub mod home; pub mod recipe_edit; pub mod recipe_view; diff --git a/frontend/src/pages/recipe_edit.rs b/frontend/src/pages/recipe_edit.rs index 721547d..858825b 100644 --- a/frontend/src/pages/recipe_edit.rs +++ b/frontend/src/pages/recipe_edit.rs @@ -446,6 +446,7 @@ where continue; } let tag_span = document().create_element("span").unwrap(); + tag_span.set_class_name("tag"); tag_span.set_inner_html(&tag); let delete_tag_button: HtmlInputElement = document() .create_element("input") @@ -453,7 +454,7 @@ where .dyn_into() .unwrap(); delete_tag_button.set_attribute("type", "button").unwrap(); - delete_tag_button.set_attribute("value", "X").unwrap(); + delete_tag_button.set_attribute("value", "✖").unwrap(); tag_span.append_child(&delete_tag_button).unwrap(); tags_span.append_child(&tag_span).unwrap();