From: Greg Burri Date: Wed, 29 Jan 2025 13:37:25 +0000 (+0100) Subject: Calendar (WIP) X-Git-Url: https://git.euphorik.ch/?a=commitdiff_plain;h=79a0aeb1b8e6ff25da394fccd843422264e264ca;p=recipes.git Calendar (WIP) --- diff --git a/Cargo.lock b/Cargo.lock index a53d261..04b5c9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,7 +26,7 @@ dependencies = [ "cfg-if", "once_cell", "version_check", - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -315,9 +315,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "byteorder" @@ -356,6 +356,7 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", "windows-targets 0.52.6", ] @@ -420,6 +421,7 @@ checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" name = "common" version = "0.1.0" dependencies = [ + "chrono", "ron", "serde", ] @@ -477,9 +479,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] @@ -832,10 +834,22 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets 0.52.6", +] + [[package]] name = "gimli" version = "0.31.1" @@ -912,7 +926,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "903f432be5ba34427eac5e16048ef65604a82061fe93789f2212afc73d8617d6" dependencies = [ - "getrandom", + "getrandom 0.2.15", "gloo-events", "gloo-utils", "serde", @@ -1156,9 +1170,9 @@ checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" [[package]] name = "httparse" -version = "1.9.5" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" +checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" [[package]] name = "httpdate" @@ -1177,9 +1191,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.5.2" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" dependencies = [ "bytes", "futures-channel", @@ -1573,7 +1587,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] @@ -1609,7 +1623,7 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand", + "rand 0.8.5", "smallvec", "zeroize", ] @@ -1707,7 +1721,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -1808,7 +1822,7 @@ version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -1861,8 +1875,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.0", + "zerocopy 0.8.14", ] [[package]] @@ -1872,7 +1897,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.0", ] [[package]] @@ -1881,7 +1916,17 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.15", +] + +[[package]] +name = "rand_core" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff" +dependencies = [ + "getrandom 0.3.1", + "zerocopy 0.8.14", ] [[package]] @@ -1897,8 +1942,8 @@ dependencies = [ "derive_more", "itertools", "lettre", - "rand", - "rand_core", + "rand 0.9.0", + "rand_core 0.9.0", "rinja", "ron", "serde", @@ -1974,7 +2019,7 @@ checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", "cfg-if", - "getrandom", + "getrandom 0.2.15", "libc", "spin", "untrusted", @@ -2047,7 +2092,7 @@ dependencies = [ "num-traits", "pkcs1", "pkcs8", - "rand_core", + "rand_core 0.6.4", "signature", "spki", "subtle", @@ -2105,9 +2150,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" [[package]] name = "rustls-webpki" @@ -2128,9 +2173,9 @@ checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" [[package]] name = "scopeguard" @@ -2171,9 +2216,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.137" +version = "1.0.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b" +checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" dependencies = [ "itoa", "memchr", @@ -2256,7 +2301,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -2422,7 +2467,7 @@ dependencies = [ "memchr", "once_cell", "percent-encoding", - "rand", + "rand 0.8.5", "rsa", "serde", "sha1", @@ -2461,7 +2506,7 @@ dependencies = [ "md-5", "memchr", "once_cell", - "rand", + "rand 0.8.5", "serde", "serde_json", "sha2", @@ -2588,13 +2633,13 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.15.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" +checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" dependencies = [ "cfg-if", "fastrand", - "getrandom", + "getrandom 0.3.1", "once_cell", "rustix", "windows-sys 0.59.0", @@ -2921,9 +2966,9 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" [[package]] name = "unicode-normalization" @@ -3011,6 +3056,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasite" version = "0.1.0" @@ -3315,6 +3369,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags", +] + [[package]] name = "write16" version = "1.0.0" @@ -3358,7 +3421,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", - "zerocopy-derive", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a367f292d93d4eab890745e75a778da40909cab4d6ff8173693812f79c4a2468" +dependencies = [ + "zerocopy-derive 0.8.14", ] [[package]] @@ -3372,6 +3444,17 @@ dependencies = [ "syn", ] +[[package]] +name = "zerocopy-derive" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3931cb58c62c13adec22e38686b559c86a30565e16ad6e8510a337cedc611e1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zerofrom" version = "0.1.5" diff --git a/TODO.md b/TODO.md index 4f59ace..5163115 100644 --- a/TODO.md +++ b/TODO.md @@ -1,9 +1,14 @@ * FIX: when the event blur is triggered when changing page, the async process doesn't finish all the time +* User can change default_servings in profile +* Can choose servings number in recipe view + * Default number is the user setting user.default_servings + * A symbol show the native recipe servings number * Check position of message error in profile/sign in/sign up with flex grid layout * Define the UI (mockups). * Two CSS: one for desktop and one for mobile * Use CSS flex/grid to define a good design/layout * CSS for toast and modal dialog +* Calendar: Choose the first day of the week * Make a search page Use FTS5: https://sqlite.org/fts5.html @@ -13,6 +18,10 @@ * Make the home page: Define what to display to the user * Show existing tags when editing a recipe +[ok] Add a table for website global settings with two column: name + value + * Add a boolean settings to enable/disable new inscription +[ok] Add a [is_admin] flag to [User] table +[ok] Test when there is an SQL error (syntax error for sample) [ok] Drag and drop of steps and groups to define their order [ok] Force tags in lowercase [ok] Remove the given language to recipe_edit and replace it by tr (like the header) diff --git a/backend/Cargo.toml b/backend/Cargo.toml index cc6a499..b52bb93 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -16,7 +16,7 @@ tower-http = { version = "0.6", features = ["fs", "trace"] } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } -chrono = "0.4" +chrono = { version = "0.4", features = ["serde"] } # Rust object notation, to load configuration files. ron = "0.8" @@ -30,8 +30,8 @@ sqlx = { version = "0.8", features = ["sqlite", "runtime-tokio", "chrono"] } rinja = { version = "0.3" } argon2 = { version = "0.5", features = ["default", "std"] } -rand_core = { version = "0.6", features = ["std"] } -rand = "0.8" +rand_core = { version = "0.9", features = ["std"] } +rand = "0.9" strum = "0.26" strum_macros = "0.26" diff --git a/backend/scss/calendar.scss b/backend/scss/calendar.scss index 6500a85..77e5a9b 100644 --- a/backend/scss/calendar.scss +++ b/backend/scss/calendar.scss @@ -41,6 +41,10 @@ width: 14%; text-align: center; margin: 0; + + &.current-month { + background-color: blue; + } } } } \ No newline at end of file diff --git a/backend/sql/version_1.sql b/backend/sql/version_1.sql index 0100ee3..07d7d60 100644 --- a/backend/sql/version_1.sql +++ b/backend/sql/version_1.sql @@ -172,6 +172,8 @@ CREATE TABLE [RecipeScheduled] ( FOREIGN KEY([recipe_id]) REFERENCES [Recipe]([id]) ON DELETE CASCADE ); +CREATE INDEX [RecipeScheduled_date_index] ON [RecipeScheduled]([date]); + CREATE TABLE [ShoppingEntry] ( [id] INTEGER PRIMARY KEY, [user_id] INTEGER NOT NULL, diff --git a/backend/src/data/db/recipe.rs b/backend/src/data/db/recipe.rs index d1e1a8f..d7b8978 100644 --- a/backend/src/data/db/recipe.rs +++ b/backend/src/data/db/recipe.rs @@ -1,10 +1,9 @@ -use chrono::prelude::*; +use chrono::{prelude::*, Days}; +use common::ron_api::Difficulty; use itertools::Itertools; use super::{Connection, DBError, Result}; -use crate::data::model; - -use common::ron_api::Difficulty; +use crate::{data::model, user_authentication}; impl Connection { /// Returns all the recipe titles where recipe is written in the given language. @@ -106,11 +105,10 @@ SELECT COUNT(*) FROM [Recipe] INNER JOIN [User] ON [User].[id] = [Recipe].[user_id] INNER JOIN [Group] ON [Group].[recipe_id] = [Recipe].[id] -WHERE [Group].[id] IN ({}) AND ([user_id] = $2 OR (SELECT [is_admin] FROM [User] WHERE [id] = $2)) +WHERE [Group].[id] IN ({}) AND ([user_id] = $1 OR (SELECT [is_admin] FROM [User] WHERE [id] = $1)) "#, params ); - let mut query = sqlx::query_scalar::<_, u64>(&query_str).bind(user_id); for id in group_ids { query = query.bind(id); @@ -147,11 +145,10 @@ FROM [Recipe] INNER JOIN [User] ON [User].[id] = [Recipe].[user_id] INNER JOIN [Group] ON [Group].[recipe_id] = [Recipe].[id] INNER JOIN [Step] ON [Step].[group_id] = [Group].[id] -WHERE [Step].[id] IN ({}) AND ([user_id] = $2 OR (SELECT [is_admin] FROM [User] WHERE [id] = $2)) +WHERE [Step].[id] IN ({}) AND ([user_id] = $1 OR (SELECT [is_admin] FROM [User] WHERE [id] = $1)) "#, params ); - let mut query = sqlx::query_scalar::<_, u64>(&query_str).bind(user_id); for id in steps_ids { query = query.bind(id); @@ -199,11 +196,10 @@ INNER JOIN [Group] ON [Group].[recipe_id] = [Recipe].[id] INNER JOIN [Step] ON [Step].[group_id] = [Group].[id] INNER JOIN [Ingredient] ON [Ingredient].[step_id] = [Step].[id] WHERE [Ingredient].[id] IN ({}) AND - ([user_id] = $2 OR (SELECT [is_admin] FROM [User] WHERE [id] = $2)) + ([user_id] = $1 OR (SELECT [is_admin] FROM [User] WHERE [id] = $1)) "#, params ); - let mut query = sqlx::query_scalar::<_, u64>(&query_str).bind(user_id); for id in ingredients_ids { query = query.bind(id); @@ -755,6 +751,73 @@ VALUES ($1, $2) Ok(()) } + + pub async fn add_schedule_recipe( + &self, + user_id: i64, + recipe_id: i64, + date: NaiveDate, + servings: u32, + ) -> Result<()> { + sqlx::query( + r#" +INSERT INTO [RecipeScheduled] (user_id, recipe_id, date, servings) +VALUES ($1, $2, $3, $4) + "#, + ) + .bind(user_id) + .bind(recipe_id) + .bind(date) + .bind(servings) + .execute(&self.pool) + .await + .map(|_| ()) + .map_err(DBError::from) + } + + pub async fn remove_scheduled_recipe( + &self, + user_id: i64, + recipe_id: i64, + date: NaiveDate, + ) -> Result<()> { + sqlx::query( + r#" +DELETE FROM [RecipeScheduled] +WHERE [user_id] = $1 AND [recipe_id] = $2 AND [date] = $3 + "#, + ) + .bind(user_id) + .bind(recipe_id) + .bind(date) + .execute(&self.pool) + .await + .map(|_| ()) + .map_err(DBError::from) + } + + pub async fn get_scheduled_recipes( + &self, + user_id: i64, + start_date: NaiveDate, + end_date: NaiveDate, + ) -> Result> { + sqlx::query_as( + r#" +SELECT [date], [Recipe].[title], [Recipe].[id], [RecipeScheduled].[date] +FROM [RecipeScheduled] +INNER JOIN [Recipe] ON [Recipe].[id] = [RecipeScheduled].[recipe_id] +WHERE [RecipeScheduled].[user_id] = $1 AND [date] >= $2 AND [date] <= $3 +ORDER BY [date] + "#, + ) + .bind(user_id) + .bind(start_date) + .bind(end_date) + .fetch_all(&self.pool) + .await + .map_err(DBError::from) + } } #[cfg(test)] @@ -884,4 +947,83 @@ VALUES Ok(()) } + + #[tokio::test] + async fn schedule_recipe() -> Result<()> { + let connection = Connection::new_in_memory().await?; + let user_id = create_a_user(&connection).await?; + + let recipe_id_1 = connection.create_recipe(user_id).await?; + connection.set_recipe_title(recipe_id_1, "recipe 1").await?; + + let recipe_id_2 = connection.create_recipe(user_id).await?; + connection.set_recipe_title(recipe_id_2, "recipe 2").await?; + + let today = NaiveDate::from_ymd_opt(2025, 1, 23).unwrap(); + let yesterday = today - Days::new(1); + let tomorrow = today + Days::new(1); + + connection + .add_schedule_recipe(user_id, recipe_id_1, today, 4) + .await?; + connection + .add_schedule_recipe(user_id, recipe_id_2, yesterday, 4) + .await?; + connection + .add_schedule_recipe(user_id, recipe_id_1, tomorrow, 4) + .await?; + + assert_eq!( + connection + .get_scheduled_recipes(user_id, today, today) + .await?, + vec![( + NaiveDate::from_ymd_opt(2025, 1, 23).unwrap(), + "recipe 1".to_string(), + 1 + )] + ); + + assert_eq!( + connection + .get_scheduled_recipes(user_id, yesterday, tomorrow) + .await?, + vec![ + ( + NaiveDate::from_ymd_opt(2025, 1, 22).unwrap(), + "recipe 2".to_string(), + 2 + ), + ( + NaiveDate::from_ymd_opt(2025, 1, 23).unwrap(), + "recipe 1".to_string(), + 1 + ), + ( + NaiveDate::from_ymd_opt(2025, 1, 24).unwrap(), + "recipe 1".to_string(), + 1 + ) + ] + ); + + connection + .remove_scheduled_recipe(user_id, recipe_id_1, today) + .await?; + connection + .remove_scheduled_recipe(user_id, recipe_id_2, yesterday) + .await?; + connection + .remove_scheduled_recipe(user_id, recipe_id_1, tomorrow) + .await?; + + assert_eq!( + connection + .get_scheduled_recipes(user_id, yesterday, tomorrow) + .await?, + vec![] + ); + + Ok(()) + } } diff --git a/backend/src/data/db/user.rs b/backend/src/data/db/user.rs index 4b7d2b4..4e1768e 100644 --- a/backend/src/data/db/user.rs +++ b/backend/src/data/db/user.rs @@ -1,5 +1,5 @@ use chrono::{prelude::*, Duration}; -use rand::distributions::{Alphanumeric, DistString}; +use rand::distr::{Alphanumeric, SampleString}; use sqlx::Sqlite; use super::{Connection, DBError, Result}; @@ -57,7 +57,7 @@ pub enum ResetPasswordResult { } fn generate_token() -> String { - Alphanumeric.sample_string(&mut rand::thread_rng(), consts::TOKEN_SIZE) + Alphanumeric.sample_string(&mut rand::rng(), consts::TOKEN_SIZE) } impl Connection { diff --git a/backend/src/main.rs b/backend/src/main.rs index c5bcb30..4c07c00 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -177,6 +177,10 @@ async fn main() { "/recipe/set_ingredients_order", put(services::ron::set_ingredients_order), ) + .route( + "/calendar/get_scheduled_recipes", + get(services::ron::get_scheduled_recipes), + ) .fallback(services::ron::not_found); let fragments_routes = Router::new().route( diff --git a/backend/src/services/ron.rs b/backend/src/services/ron.rs index a6c6238..bedccb1 100644 --- a/backend/src/services/ron.rs +++ b/backend/src/services/ron.rs @@ -5,6 +5,7 @@ use axum::{ response::{ErrorResponse, IntoResponse, Result}, }; use axum_extra::extract::cookie::{Cookie, CookieJar}; +use chrono::NaiveDate; use serde::Deserialize; // use tracing::{event, Level}; @@ -183,11 +184,11 @@ async fn check_user_rights_recipe_ingredient( async fn check_user_rights_recipe_ingredients( connection: &db::Connection, user: &Option, - step_ids: &[i64], + ingredient_ids: &[i64], ) -> Result<()> { if user.is_none() || !connection - .can_edit_recipe_all_ingredients(user.as_ref().unwrap().id, step_ids) + .can_edit_recipe_all_ingredients(user.as_ref().unwrap().id, ingredient_ids) .await? { Err(ErrorResponse::from(ron_error( @@ -599,7 +600,39 @@ pub async fn set_ingredients_order( Ok(StatusCode::OK) } -///// 404 ///// +/// Calendar /// + +#[derive(Deserialize)] +pub struct DateRange { + start_date: NaiveDate, + end_date: NaiveDate, +} + +#[debug_handler] +pub async fn get_scheduled_recipes( + State(connection): State, + Extension(user): Extension>, + date_range: Query, +) -> Result { + if let Some(user) = user { + Ok(ron_response( + StatusCode::OK, + common::ron_api::ScheduledRecipes { + recipes: connection + .get_scheduled_recipes(user.id, date_range.start_date, date_range.end_date) + .await?, + }, + )) + } else { + Err(ErrorResponse::from(ron_error( + StatusCode::UNAUTHORIZED, + NOT_AUTHORIZED_MESSAGE, + ))) + } +} + +/// 404 /// + #[debug_handler] pub async fn not_found(Extension(_user): Extension>) -> impl IntoResponse { ron_error(StatusCode::NOT_FOUND, "Not found") diff --git a/backend/src/translation.rs b/backend/src/translation.rs index 0e4383f..9fc14c5 100644 --- a/backend/src/translation.rs +++ b/backend/src/translation.rs @@ -1,12 +1,13 @@ use std::{borrow::Borrow, fs::File, sync::LazyLock}; +use common::utils; use ron::de::from_reader; use serde::Deserialize; use strum::EnumCount; use strum_macros::EnumCount; use tracing::{event, Level}; -use crate::{consts, utils}; +use crate::consts; #[derive(Debug, Clone, EnumCount, Deserialize)] pub enum Sentence { @@ -109,6 +110,10 @@ pub enum Sentence { RecipeIngredientQuantity, RecipeIngredientUnit, RecipeIngredientComment, + RecipeDeleteConfirmation, + RecipeGroupDeleteConfirmation, + RecipeStepDeleteConfirmation, + RecipeIngredientDeleteConfirmation, // View Recipe. RecipeOneServing, diff --git a/backend/src/utils.rs b/backend/src/utils.rs index fddeb71..f8715f3 100644 --- a/backend/src/utils.rs +++ b/backend/src/utils.rs @@ -39,44 +39,3 @@ pub fn get_url_from_host(host: &str) -> String { host ) } - -pub fn substitute(str: &str, pattern: &str, replacements: &[&str]) -> String { - let mut result = String::with_capacity( - (str.len() + replacements.iter().map(|s| s.len()).sum::()) - .saturating_sub(pattern.len() * replacements.len()), - ); - - let mut i = 0; - for s in str.split(pattern) { - result.push_str(s); - if i < replacements.len() { - result.push_str(replacements[i]); - } - i += 1; - } - - if i == 1 { - return str.to_string(); - } - - result -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_substitute() { - assert_eq!(substitute("", "", &[]), ""); - assert_eq!(substitute("", "", &[""]), ""); - assert_eq!(substitute("", "{}", &["a"]), ""); - assert_eq!(substitute("a", "{}", &["b"]), "a"); - assert_eq!(substitute("a{}", "{}", &["b"]), "ab"); - assert_eq!(substitute("{}c", "{}", &["b"]), "bc"); - assert_eq!(substitute("a{}c", "{}", &["b"]), "abc"); - assert_eq!(substitute("{}b{}", "{}", &["a", "c"]), "abc"); - assert_eq!(substitute("{}{}{}", "{}", &["a", "bc", "def"]), "abcdef"); - assert_eq!(substitute("{}{}{}", "{}", &["a"]), "a"); - } -} diff --git a/backend/templates/calendar.html b/backend/templates/calendar.html index 5183ea5..435e48e 100644 --- a/backend/templates/calendar.html +++ b/backend/templates/calendar.html @@ -38,7 +38,7 @@
    {% for i in 0..7 %} {% for j in 0..5 %} -
  • +
  • {% endfor %} {% endfor %}
diff --git a/backend/templates/recipe_edit.html b/backend/templates/recipe_edit.html index c14c8fc..502b37e 100644 --- a/backend/templates/recipe_edit.html +++ b/backend/templates/recipe_edit.html @@ -134,6 +134,11 @@
+ + {{ tr.t(Sentence::RecipeDeleteConfirmation) }} + {{ tr.t(Sentence::RecipeGroupDeleteConfirmation) }} + {{ tr.t(Sentence::RecipeStepDeleteConfirmation) }} + {{ tr.t(Sentence::RecipeIngredientDeleteConfirmation) }} diff --git a/backend/translation.ron b/backend/translation.ron index 168d8de..8089431 100644 --- a/backend/translation.ron +++ b/backend/translation.ron @@ -95,6 +95,10 @@ (RecipeIngredientQuantity, "Quantity"), (RecipeIngredientUnit, "Unit"), (RecipeIngredientComment, "Comment"), + (RecipeDeleteConfirmation, "Are you sure to delete the recipe: '{}'?"), + (RecipeGroupDeleteConfirmation, "Are you sure to delete the group: '{}'?"), + (RecipeStepDeleteConfirmation, "Are you sure to delete the step: '{}'?"), + (RecipeIngredientDeleteConfirmation, "Are you sure to delete the ingredient: '{}'?"), (RecipeOneServing, "1 serving"), (RecipeSomeServings, "{} servings"), @@ -217,6 +221,10 @@ (RecipeIngredientQuantity, "Quantité"), (RecipeIngredientUnit, "Unité"), (RecipeIngredientComment, "Commentaire"), + (RecipeDeleteConfirmation, "Êtes-vous sûr de vouloir supprimer la recette : '{}' ?"), + (RecipeGroupDeleteConfirmation, "Êtes-vous sûr de vouloir supprimer le groupe : '{}' ?"), + (RecipeStepDeleteConfirmation, "Êtes-vous sûr de vouloir supprimer l'étape : '{}' ?"), + (RecipeIngredientDeleteConfirmation, "Êtes-vous sûr de vouloir supprimer 'ingrédient : '{}' ?"), (RecipeOneServing, "pour 1 personne"), (RecipeSomeServings, "pour {} personnes"), diff --git a/common/Cargo.toml b/common/Cargo.toml index efb39b9..ea07afa 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -7,3 +7,4 @@ edition = "2021" [dependencies] ron = "0.8" serde = { version = "1.0", features = ["derive"] } +chrono = { version = "0.4", features = ["serde"] } diff --git a/common/src/ron_api.rs b/common/src/ron_api.rs index a088220..5ca697f 100644 --- a/common/src/ron_api.rs +++ b/common/src/ron_api.rs @@ -1,3 +1,4 @@ +use chrono::NaiveDate; use ron::ser::{to_string_pretty, PrettyConfig}; use serde::{Deserialize, Serialize}; @@ -16,7 +17,7 @@ pub struct Id { pub id: i64, } -///// RECIPE ///// +/// RECIPE /// #[derive(Serialize, Deserialize, Clone)] pub struct SetRecipeTitle { @@ -158,7 +159,7 @@ pub struct Ingredient { pub quantity_unit: String, } -///// PROFILE ///// +/// PROFILE /// #[derive(Serialize, Deserialize, Clone)] pub struct UpdateProfile { @@ -174,3 +175,11 @@ where // TODO: handle'unwrap'. to_string_pretty(&ron, PrettyConfig::new()).unwrap() } + +/// Calendar /// + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct ScheduledRecipes { + // (Scheduled date, recipe title, recipe id). + pub recipes: Vec<(NaiveDate, String, i64)>, +} diff --git a/common/src/utils.rs b/common/src/utils.rs index 8b24810..2efe599 100644 --- a/common/src/utils.rs +++ b/common/src/utils.rs @@ -12,3 +12,44 @@ pub fn validate_password(password: &str) -> PasswordValidation { PasswordValidation::Ok } } + +pub fn substitute(str: &str, pattern: &str, replacements: &[&str]) -> String { + let mut result = String::with_capacity( + (str.len() + replacements.iter().map(|s| s.len()).sum::()) + .saturating_sub(pattern.len() * replacements.len()), + ); + + let mut i = 0; + for s in str.split(pattern) { + result.push_str(s); + if i < replacements.len() { + result.push_str(replacements[i]); + } + i += 1; + } + + if i == 1 { + return str.to_string(); + } + + result +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_substitute() { + assert_eq!(substitute("", "", &[]), ""); + assert_eq!(substitute("", "", &[""]), ""); + assert_eq!(substitute("", "{}", &["a"]), ""); + assert_eq!(substitute("a", "{}", &["b"]), "a"); + assert_eq!(substitute("a{}", "{}", &["b"]), "ab"); + assert_eq!(substitute("{}c", "{}", &["b"]), "bc"); + assert_eq!(substitute("a{}c", "{}", &["b"]), "abc"); + assert_eq!(substitute("{}b{}", "{}", &["a", "c"]), "abc"); + assert_eq!(substitute("{}{}{}", "{}", &["a", "bc", "def"]), "abcdef"); + assert_eq!(substitute("{}{}{}", "{}", &["a"]), "a"); + } +} diff --git a/frontend/Cargo.toml b/frontend/Cargo.toml index 4214b2f..adc06e1 100644 --- a/frontend/Cargo.toml +++ b/frontend/Cargo.toml @@ -13,7 +13,7 @@ default = ["console_error_panic_hook"] [dependencies] common = { path = "../common" } -chrono = "0.4" +chrono = { version = "0.4", features = ["serde"] } ron = "0.8" serde = { version = "1.0", features = ["derive"] } diff --git a/frontend/src/calendar.rs b/frontend/src/calendar.rs index 7ace6d3..b715117 100644 --- a/frontend/src/calendar.rs +++ b/frontend/src/calendar.rs @@ -1,100 +1,144 @@ -use std::{ - ops::{AddAssign, SubAssign}, - sync::{ - atomic::{AtomicI32, AtomicU32, Ordering}, - Arc, - }, +use std::sync::{ + atomic::{AtomicI32, AtomicU32, Ordering}, + Arc, Mutex, }; -use chrono::{offset::Local, Datelike, Days, NaiveDate, Weekday}; +use chrono::{offset::Local, DateTime, Datelike, Days, Months, NaiveDate, Weekday}; +use common::ron_api; use gloo::{console::log, events::EventListener}; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::spawn_local; use web_sys::Element; -use crate::utils::{by_id, SelectorExt}; +use crate::{ + request, + utils::{by_id, selector, SelectorExt}, +}; + +struct CalendarStateInternal { + current_date: DateTime, + selected_date: DateTime, +} + +#[derive(Clone)] +struct CalendarState { + internal_state: Arc>, +} + +impl CalendarState { + pub fn new() -> Self { + let current_date = Local::now(); + Self { + internal_state: Arc::new(Mutex::new(CalendarStateInternal { + current_date, + selected_date: current_date, + })), + } + } + + pub fn to_next_month(&self) -> DateTime { + let mut locker = self.internal_state.lock().unwrap(); + let new_date = locker + .current_date + .checked_add_months(Months::new(1)) + .unwrap(); + locker.current_date = new_date; + new_date + } + + pub fn to_previous_month(&self) -> DateTime { + let mut locker = self.internal_state.lock().unwrap(); + let new_date = locker + .current_date + .checked_sub_months(Months::new(1)) + .unwrap(); + locker.current_date = new_date; + new_date + } + + pub fn get_current_date(&self) -> DateTime { + self.internal_state.lock().unwrap().current_date + } + + pub fn get_selected_date(&self) -> DateTime { + self.internal_state.lock().unwrap().selected_date + } +} -pub fn setup(calendar: &Element) { +pub fn setup(calendar: Element) { let prev: Element = calendar.selector(".prev"); let next: Element = calendar.selector(".next"); - let current_month = Arc::new(AtomicU32::new(Local::now().month())); - let current_year = Arc::new(AtomicI32::new(Local::now().year())); + let state = CalendarState::new(); - display_month(calendar, Local::now().year(), Local::now().month()); + display_month(&calendar, state.get_current_date()); let calendar_clone = calendar.clone(); - let current_month_clone = current_month.clone(); - let current_year_clone = current_year.clone(); + let state_clone = state.clone(); EventListener::new(&prev, "click", move |_event| { - let mut m = current_month_clone.load(Ordering::Relaxed) - 1; - if m == 0 { - current_year_clone.fetch_sub(1, Ordering::Relaxed); - m = 12 - } - current_month_clone.store(m, Ordering::Relaxed); - display_month( - &calendar_clone, - current_year_clone.load(Ordering::Relaxed), - m, - ); + let m = state_clone.to_previous_month(); + display_month(&calendar_clone, m); }) .forget(); let calendar_clone = calendar.clone(); - let current_month_clone = current_month.clone(); - let current_year_clone = current_year.clone(); + let state_clone = state.clone(); EventListener::new(&next, "click", move |_event| { - let mut m = current_month_clone.load(Ordering::Relaxed) + 1; - if m == 13 { - current_year_clone.fetch_add(1, Ordering::Relaxed); - m = 1 - } - current_month_clone.store(m, Ordering::Relaxed); - display_month( - &calendar_clone, - current_year_clone.load(Ordering::Relaxed), - m, - ); + let m = state_clone.to_next_month(); + display_month(&calendar_clone, m); }) .forget(); - // now.weekday() - - // console!(now.to_string()); + // let days: Element = calendar.selector(".days"); + // let state_clone = state.clone(); + // EventListener::new(&days, "click", move |event| { + // log!(event); + // let target: Element = event.target().unwrap().dyn_into().unwrap(); + // if + // }) + // .forget(); + + // let calendar_clone = calendar.clone(); + // let current_month_clone = current_month.clone(); + // let current_year_clone = current_year.clone(); + // EventListener::new(&next, "click", move |_event| { + // let mut m = current_month_clone.load(Ordering::Relaxed) + 1; + // if m == 13 { + // current_year_clone.fetch_add(1, Ordering::Relaxed); + // m = 1 + // } + // current_month_clone.store(m, Ordering::Relaxed); + // display_month( + // &calendar_clone, + // current_year_clone.load(Ordering::Relaxed), + // m, + // ); + // }) + // .forget(); } -// fn translate_month(month: u32) -> &'static str { -// match -// } - -fn display_month(calendar: &Element, year: i32, month: u32) { - log!(year, month); +const NB_CALENDAR_ROW: u64 = 5; +fn display_month(calendar: &Element, date: DateTime) { calendar .selector::(".year") - .set_inner_html(&year.to_string()); + .set_inner_html(&date.year().to_string()); for (i, m) in calendar .selector_all::(".month") .into_iter() .enumerate() { - if i as u32 + 1 == month { + if i as u32 + 1 == date.month() { m.set_class_name("month current"); } else { m.set_class_name("month"); } } - // calendar - // .selector::(".month") - // .set_inner_html(&month.to_string()); - - let mut current = NaiveDate::from_ymd_opt(year, month, 1).unwrap(); + let mut current = date; - // let mut day = Local:: ; - while (current - Days::new(1)).month() == month { + while (current - Days::new(1)).month() == date.month() { current = current - Days::new(1); } @@ -102,20 +146,46 @@ fn display_month(calendar: &Element, year: i32, month: u32) { current = current - Days::new(1); } + let first_day = current; + for i in 0..7 { - for j in 0..5 { - let li: Element = by_id(&format!("day-{}{}", i, j)); - li.set_inner_html(¤t.day().to_string()); - - if current == Local::now().date_naive() { - li.set_class_name("current-month today"); - } else if current.month() == month { - li.set_class_name("current-month"); + for j in 0..NB_CALENDAR_ROW { + let day_element: Element = by_id(&format!("day-{}{}", i, j)); + let day_content: Element = day_element.selector(".number"); + day_content.set_inner_html(¤t.day().to_string()); + + if current == Local::now() { + day_element.set_class_name("current-month today"); + } else if current.month() == date.month() { + day_element.set_class_name("current-month"); } else { - li.set_class_name(""); + day_element.set_class_name(""); } current = current + Days::new(1); } } + + spawn_local(async move { + let scheduled_recipes: ron_api::ScheduledRecipes = request::get( + "calendar/get_scheduled_recipes", + [ + ("start_date", first_day.date_naive().to_string()), + ( + "end_date", + (first_day + Days::new(NB_CALENDAR_ROW * 7)) + .date_naive() + .to_string(), + ), + ], + ) + .await + .unwrap(); + + for recipe in scheduled_recipes.recipes { + log!(recipe.1); + } + + // create_tag_elements(recipe_id, &tags.tags); + }); } diff --git a/frontend/src/lib.rs b/frontend/src/lib.rs index 588be31..4ac25e3 100644 --- a/frontend/src/lib.rs +++ b/frontend/src/lib.rs @@ -1,3 +1,10 @@ +use common::ron_api; +use gloo::{console::log, events::EventListener, utils::window}; +use utils::by_id; +use wasm_bindgen::prelude::*; +use wasm_bindgen_futures::spawn_local; +use web_sys::HtmlSelectElement; + mod calendar; mod modal_dialog; mod on_click; @@ -7,14 +14,6 @@ mod request; mod toast; mod utils; -use gloo::{console::log, events::EventListener, utils::window}; -use utils::by_id; -use wasm_bindgen::prelude::*; -use wasm_bindgen_futures::spawn_local; -use web_sys::HtmlSelectElement; - -use common::ron_api; - #[wasm_bindgen(start)] pub fn main() -> Result<(), JsValue> { console_error_panic_hook::set_once(); diff --git a/frontend/src/modal_dialog.rs b/frontend/src/modal_dialog.rs index ac83ab9..477df12 100644 --- a/frontend/src/modal_dialog.rs +++ b/frontend/src/modal_dialog.rs @@ -6,17 +6,13 @@ use crate::{ utils::{by_id, selector_and_clone, SelectorExt}, }; -pub enum DialogContent<'a, T> -where - T: Fn(&Element), -{ - Text(&'a str), - CloneFromElement(&'a str, T), +pub async fn show(element_selector: &str) -> bool { + show_and_initialize(element_selector, async |_| {}).await } -pub async fn show(content: DialogContent<'_, T>) -> bool +pub async fn show_and_initialize(element_selector: &str, initializer: T) -> bool where - T: Fn(&Element), + T: AsyncFn(Element), { let dialog: HtmlDialogElement = by_id("modal-dialog"); @@ -25,15 +21,10 @@ where let content_element = dialog.selector::(".content"); - match content { - DialogContent::Text(message) => content_element.set_inner_html(message), - DialogContent::CloneFromElement(element_selector, initilizer) => { - let element: Element = selector_and_clone(element_selector); - content_element.set_inner_html(""); - content_element.append_child(&element).unwrap(); - initilizer(&element); - } - } + let element: Element = selector_and_clone(element_selector); + content_element.set_inner_html(""); + content_element.append_child(&element).unwrap(); + initializer(element).await; dialog.show_modal().unwrap(); diff --git a/frontend/src/recipe_edit.rs b/frontend/src/recipe_edit.rs index d1a4e4c..d9c2c5a 100644 --- a/frontend/src/recipe_edit.rs +++ b/frontend/src/recipe_edit.rs @@ -1,5 +1,6 @@ use std::{cell::RefCell, rc, sync::Mutex}; +use common::{ron_api, utils::substitute}; use gloo::{ events::{EventListener, EventListenerOptions}, net::http::Request, @@ -12,14 +13,17 @@ use web_sys::{ KeyboardEvent, }; -use common::ron_api; - use crate::{ modal_dialog, request, toast::{self, Level}, utils::{by_id, selector, selector_and_clone, SelectorExt}, }; +use futures::{ + future::{FutureExt, Ready}, + pin_mut, select, Future, +}; + pub fn setup_page(recipe_id: i64) -> Result<(), JsValue> { // Title. { @@ -248,12 +252,18 @@ pub fn setup_page(recipe_id: i64) -> Result<(), JsValue> { // 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(modal_dialog::DialogContent::::Text(&format!( - "Are you sure to delete the recipe '{}'", - title.value() - ))) + if modal_dialog::show_and_initialize( + "#hidden-templates .recipe-delete-confirmation", + async |element| { + let title: HtmlInputElement = by_id("input-title"); + element.set_inner_html(&substitute( + &element.inner_html(), + "{}", + &[&title.value()], + )); + }, + ) .await { let body = ron_api::Id { id: recipe_id }; @@ -377,14 +387,18 @@ fn create_group_element(group: &ron_api::Group) -> Element { let group_element_cloned = group_element.clone(); let delete_button: HtmlInputElement = group_element.selector(".input-group-delete"); EventListener::new(&delete_button, "click", move |_event| { - let name = group_element_cloned - .selector::(".input-group-name") - .value(); + // FIXME: How to avoid cloning twice? + let group_element_cloned = group_element_cloned.clone(); spawn_local(async move { - if modal_dialog::show(modal_dialog::DialogContent::::Text(&format!( - "Are you sure to delete the group '{}'", - name - ))) + if modal_dialog::show_and_initialize( + "#hidden-templates .recipe-group-delete-confirmation", + async move |element| { + let name = group_element_cloned + .selector::(".input-group-name") + .value(); + element.set_inner_html(&substitute(&element.inner_html(), "{}", &[&name])); + }, + ) .await { let body = ron_api::Id { id: group_id }; @@ -515,14 +529,18 @@ fn create_step_element(group_element: &Element, step: &ron_api::Step) -> Element let step_element_cloned = step_element.clone(); let delete_button: HtmlInputElement = step_element.selector(".input-step-delete"); EventListener::new(&delete_button, "click", move |_event| { - let action = step_element_cloned - .selector::(".text-area-step-action") - .value(); + // FIXME: How to avoid cloning twice? + let step_element_cloned = step_element_cloned.clone(); spawn_local(async move { - if modal_dialog::show(modal_dialog::DialogContent::::Text(&format!( - "Are you sure to delete the step '{}'", - action - ))) + if modal_dialog::show_and_initialize( + "#hidden-templates .recipe-step-delete-confirmation", + async move |element| { + let action = step_element_cloned + .selector::(".text-area-step-action") + .value(); + element.set_inner_html(&substitute(&element.inner_html(), "{}", &[&action])); + }, + ) .await { let body = ron_api::Id { id: step_id }; @@ -665,14 +683,18 @@ fn create_ingredient_element(step_element: &Element, ingredient: &ron_api::Ingre let ingredient_element_cloned = ingredient_element.clone(); let delete_button: HtmlInputElement = ingredient_element.selector(".input-ingredient-delete"); EventListener::new(&delete_button, "click", move |_event| { - let name = ingredient_element_cloned - .selector::(".input-ingredient-name") - .value(); + // FIXME: How to avoid cloning twice? + let ingredient_element_cloned = ingredient_element_cloned.clone(); spawn_local(async move { - if modal_dialog::show(modal_dialog::DialogContent::::Text(&format!( - "Are you sure to delete the ingredient '{}'", - name - ))) + if modal_dialog::show_and_initialize( + "#hidden-templates .recipe-ingredient-delete-confirmation", + async move |element| { + let name = ingredient_element_cloned + .selector::(".input-ingredient-name") + .value(); + element.set_inner_html(&substitute(&element.inner_html(), "{}", &[&name])); + }, + ) .await { let body = ron_api::Id { id: ingredient_id }; diff --git a/frontend/src/recipe_view.rs b/frontend/src/recipe_view.rs index 488e459..ba30b4d 100644 --- a/frontend/src/recipe_view.rs +++ b/frontend/src/recipe_view.rs @@ -1,3 +1,6 @@ +use std::future::Future; + +use common::ron_api; use gloo::{ console::console, events::EventListener, @@ -11,8 +14,6 @@ use web_sys::{ KeyboardEvent, }; -use common::ron_api; - use crate::{ calendar, modal_dialog, request, toast::{self, Level}, @@ -22,15 +23,10 @@ use crate::{ pub fn setup_page(recipe_id: i64) -> Result<(), JsValue> { let add_to_planner: Element = selector("#recipe-view .add-to-planner"); EventListener::new(&add_to_planner, "click", move |_event| { - // console!("CLICK".to_string()); spawn_local(async move { - modal_dialog::show(modal_dialog::DialogContent::CloneFromElement( - "#hidden-templates .calendar", - |element| { - // console!("SETUP...".to_string()); - calendar::setup(element); - }, - )) + modal_dialog::show_and_initialize("#hidden-templates .calendar", async |element| { + calendar::setup(element); + }) .await; }); }) diff --git a/frontend/src/request.rs b/frontend/src/request.rs index ab4a398..306f96b 100644 --- a/frontend/src/request.rs +++ b/frontend/src/request.rs @@ -1,9 +1,8 @@ +use common::ron_api; use gloo::net::http::{Request, RequestBuilder}; use serde::{de::DeserializeOwned, Serialize}; use thiserror::Error; -use common::ron_api; - use crate::toast::{self, Level}; #[derive(Error, Debug)]