From: Greg Burri Date: Tue, 13 Dec 2022 20:01:18 +0000 (+0100) Subject: Beginning of frontend + recipe editing X-Git-Url: https://git.euphorik.ch/?a=commitdiff_plain;h=cbe276fc0601041b13087a6ffd80c5b126dfbe59;p=recipes.git Beginning of frontend + recipe editing (it's a mess) --- diff --git a/Cargo.lock b/Cargo.lock index 2db1a4b..78762f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -509,6 +509,8 @@ version = "0.1.0" dependencies = [ "lazy_static", "regex", + "ron", + "serde", ] [[package]] @@ -1212,9 +1214,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f9f08d8963a6c613f4b1a78f4f4a4dbfadf8e6545b2d72861731e4858b8b47f" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" [[package]] name = "local-channel" @@ -1407,9 +1409,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" +checksum = "cf1c2c742266c2f1041c914ba65355a83ae8747b05f208319784083583494b4b" [[package]] name = "percent-encoding" @@ -1778,18 +1780,18 @@ checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" [[package]] name = "serde" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "256b9932320c590e707b94576e3cc1f7c9024d0ee6612dfbcf1cb106cbe8e055" +checksum = "e326c9ec8042f1b5da33252c8a37e9ffbd2c9bef0155215b6e6c80c790e05f91" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4eae9b04cbffdfd550eb462ed33bc6a1b68c935127d008b27444d08380f94e4" +checksum = "42a3df25b0713732468deadad63ab9da1f1fd75a48a15024b50363f128db627e" dependencies = [ "proc-macro2", "quote", @@ -2236,9 +2238,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.22.5" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368bfe657969fb01238bb756d351dcade285e0f6fcbd36dcb23359a5169975be" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" dependencies = [ "webpki", ] diff --git a/TODO.md b/TODO.md index af94b71..efaaf3a 100644 --- a/TODO.md +++ b/TODO.md @@ -1,11 +1,13 @@ -* Change all id to i64 -* Try using WASM for all the client logic (test on signup page) +* Try using WASM for all the client logic (test on editing/creating a recipe) + * Understand the example here: + * https://github.com/rustwasm/wasm-bindgen/tree/main/examples/todomvc -> https://rustwasm.github.io/wasm-bindgen/exbuild/todomvc/#/ * Describe the use cases. * Define the UI (mockups). * Two CSS: one for desktop and one for mobile * Define the logic behind each page and action. * Add support to language into db model. +[ok] Change all id to i64 [ok] Check cookie lifetime -> Session by default [ok] Asynchonous email sending and database requests [ok] Try to return Result for async routes (and watch what is printed in log) diff --git a/backend/Cargo.toml b/backend/Cargo.toml index cc98cf5..328f1ef 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -9,11 +9,12 @@ common = {path = "../common"} actix-web = "4" actix-files = "0.6" -serde = {version = "1.0", features = ["derive"]} chrono = "0.4" ron = "0.8" # Rust object notation, to load configuration files. +serde = {version = "1.0", features = ["derive"]} + itertools = "0.10" clap = {version = "4", features = ["derive"]} diff --git a/backend/src/data/asynchronous.rs b/backend/src/data/asynchronous.rs index a5a5cdf..186dad6 100644 --- a/backend/src/data/asynchronous.rs +++ b/backend/src/data/asynchronous.rs @@ -112,4 +112,10 @@ impl Connection { let title_copy = title.to_string(); combine_errors(web::block(move || { self_copy.set_recipe_title(recipe_id, &title_copy).map_err(DBAsyncError::from) }).await) } + + pub async fn set_recipe_description_async(&self, recipe_id: i64, description: &str) -> Result<()> { + let self_copy = self.clone(); + let description_copy = description.to_string(); + combine_errors(web::block(move || { self_copy.set_recipe_description(recipe_id, &description_copy).map_err(DBAsyncError::from) }).await) + } } \ No newline at end of file diff --git a/backend/src/data/db.rs b/backend/src/data/db.rs index f16487d..5652ff3 100644 --- a/backend/src/data/db.rs +++ b/backend/src/data/db.rs @@ -3,7 +3,7 @@ use std::{fmt, fs::{self, File}, path::Path, io::Read}; use itertools::Itertools; use chrono::{prelude::*, Duration}; use rusqlite::{named_params, OptionalExtension, params, Params}; -use r2d2::Pool; +use r2d2::{Pool, PooledConnection}; use r2d2_sqlite::SqliteConnectionManager; use rand::distributions::{Alphanumeric, DistString}; @@ -109,11 +109,19 @@ impl Connection { Ok(connection) } + fn get(&self) -> Result> { + let con = self.pool.get()?; + con.pragma_update(None, "synchronous", "NORMAL")?; + Ok(con) + } + /// Called after the connection has been established for creating or updating the database. /// The 'Version' table tracks the current state of the database. fn create_or_update_db(&self) -> Result<()> { // Check the Database version. - let mut con = self.pool.get()?; + let mut con = self.get()?; + con.pragma_update(None, "journal_mode", "WAL")?; + let tx = con.transaction()?; // Version 0 corresponds to an empty database. @@ -174,7 +182,7 @@ impl Connection { } pub fn get_all_recipe_titles(&self) -> Result> { - let con = self.pool.get()?; + let con = self.get()?; let mut stmt = con.prepare("SELECT [id], [title] FROM [Recipe] ORDER BY [title]")?; @@ -188,7 +196,7 @@ impl Connection { /* Not used for the moment. pub fn get_all_recipes(&self) -> Result> { - let con = self.pool.get()?; + let con = self.get()?; let mut stmt = con.prepare("SELECT [id], [title] FROM [Recipe] ORDER BY [title]")?; let recipes = stmt.query_map([], |row| { @@ -198,14 +206,14 @@ impl Connection { } */ pub fn get_recipe(&self, id: i64) -> Result { - let con = self.pool.get()?; + let con = self.get()?; con.query_row("SELECT [id], [title], [description] FROM [Recipe] WHERE [id] = ?1", [id], |row| { Ok(model::Recipe::new(row.get("id")?, row.get("title")?, row.get("description")?)) }).map_err(DBError::from) } pub fn get_user_login_info(&self, token: &str) -> Result { - let con = self.pool.get()?; + let con = self.get()?; con.query_row("SELECT [last_login_datetime], [ip], [user_agent] FROM [UserLoginToken] WHERE [token] = ?1", [token], |r| { Ok(UserLoginInfo { last_login_datetime: r.get("last_login_datetime")?, @@ -216,7 +224,7 @@ impl Connection { } pub fn load_user(&self, user_id: i64) -> Result { - let con = self.pool.get()?; + let con = self.get()?; con.query_row("SELECT [email] FROM [User] WHERE [id] = ?1", [user_id], |r| { Ok(User { email: r.get("email")?, @@ -229,7 +237,7 @@ impl Connection { } fn sign_up_with_given_time(&self, email: &str, password: &str, datetime: DateTime) -> Result { - let mut con = self.pool.get()?; + let mut con = self.get()?; let tx = con.transaction()?; let token = match tx.query_row("SELECT [id], [validation_token] FROM [User] WHERE [email] = ?1", [email], |r| { @@ -256,7 +264,7 @@ impl Connection { } pub fn validation(&self, token: &str, validation_time: Duration, ip: &str, user_agent: &str) -> Result { - let mut con = self.pool.get()?; + let mut con = self.get()?; let tx = con.transaction()?; let user_id = match tx.query_row("SELECT [id], [creation_datetime] FROM [User] WHERE [validation_token] = ?1", [token], |r| { @@ -279,7 +287,7 @@ impl Connection { } pub fn sign_in(&self, email: &str, password: &str, ip: &str, user_agent: &str) -> Result { - let mut con = self.pool.get()?; + let mut con = self.get()?; let tx = con.transaction()?; match tx.query_row("SELECT [id], [password], [validation_token] FROM [User] WHERE [email] = ?1", [email], |r| { Ok((r.get::<&str, i64>("id")?, r.get::<&str, String>("password")?, r.get::<&str, Option>("validation_token")?)) @@ -302,7 +310,7 @@ impl Connection { } pub fn authentication(&self, token: &str, ip: &str, user_agent: &str) -> Result { - let mut con = self.pool.get()?; + let mut con = self.get()?; let tx = con.transaction()?; match tx.query_row("SELECT [id], [user_id] FROM [UserLoginToken] WHERE [token] = ?1", [token], |r| { Ok((r.get::<&str, i64>("id")?, r.get::<&str, i64>("user_id")?)) @@ -318,7 +326,7 @@ impl Connection { } pub fn sign_out(&self, token: &str) -> Result<()> { - let mut con = self.pool.get()?; + let mut con = self.get()?; let tx = con.transaction()?; match tx.query_row("SELECT [id] FROM [UserLoginToken] WHERE [token] = ?1", [token], |r| { Ok(r.get::<&str, i64>("id")?) @@ -333,7 +341,7 @@ impl Connection { } pub fn create_recipe(&self, user_id: i64) -> Result { - let con = self.pool.get()?; + let con = self.get()?; // Verify if an empty recipe already exists. Returns its id if one exists. match con.query_row( @@ -355,13 +363,18 @@ impl Connection { } pub fn set_recipe_title(&self, recipe_id: i64, title: &str) -> Result<()> { - let con = self.pool.get()?; + let con = self.get()?; con.execute("UPDATE [Recipe] SET [title] = ?2 WHERE [id] = ?1", params![recipe_id, title]).map(|_n| ()).map_err(DBError::from) } + pub fn set_recipe_description(&self, recipe_id: i64, description: &str) -> Result<()> { + let con = self.get()?; + con.execute("UPDATE [Recipe] SET [description] = ?2 WHERE [id] = ?1", params![recipe_id, description]).map(|_n| ()).map_err(DBError::from) + } + /// Execute a given SQL file. pub fn execute_file + fmt::Display>(&self, file: P) -> Result<()> { - let con = self.pool.get()?; + let con = self.get()?; let sql = load_sql_file(file)?; con.execute_batch(&sql).map_err(DBError::from) } @@ -369,7 +382,7 @@ impl Connection { /// Execute any SQL statement. /// Mainly used for testing. pub fn execute_sql(&self, sql: &str, params: P) -> Result { - let con = self.pool.get()?; + let con = self.get()?; con.execute(sql, params).map_err(DBError::from) } @@ -400,7 +413,7 @@ mod tests { #[test] fn sign_up() -> Result<()> { let connection = Connection::new_in_memory()?; - match connection.sign_up("paul@test.org", "12345")? { + match connection.sign_up("paul@atreides.com", "12345")? { SignUpResult::UserCreatedWaitingForValidation(_) => (), // Nominal case. other => panic!("{:?}", other), } @@ -414,13 +427,13 @@ mod tests { INSERT INTO [User] ([id], [email], [name], [password], [creation_datetime], [validation_token]) VALUES ( 1, - 'paul@test.org', + 'paul@atreides.com', 'paul', '$argon2id$v=19$m=4096,t=3,p=1$1vtXcacYjUHZxMrN6b2Xng$wW8Z59MIoMcsIljnjHmxn3EBcc5ymEySZPUVXHlRxcY', 0, NULL );", [])?; - match connection.sign_up("paul@test.org", "12345")? { + match connection.sign_up("paul@atreides.com", "12345")? { SignUpResult::UserAlreadyExists => (), // Nominal case. other => panic!("{:?}", other), } @@ -431,7 +444,7 @@ mod tests { fn sign_up_and_sign_in_without_validation() -> Result<()> { let connection = Connection::new_in_memory()?; - let email = "paul@test.org"; + let email = "paul@atreides.com"; let password = "12345"; match connection.sign_up(email, password)? { @@ -455,13 +468,13 @@ mod tests { INSERT INTO [User] ([id], [email], [name], [password], [creation_datetime], [validation_token]) VALUES ( 1, - 'paul@test.org', + 'paul@atreides.com', 'paul', '$argon2id$v=19$m=4096,t=3,p=1$1vtXcacYjUHZxMrN6b2Xng$wW8Z59MIoMcsIljnjHmxn3EBcc5ymEySZPUVXHlRxcY', 0, :token );", named_params! { ":token": token })?; - match connection.sign_up("paul@test.org", "12345")? { + match connection.sign_up("paul@atreides.com", "12345")? { SignUpResult::UserCreatedWaitingForValidation(_) => (), // Nominal case. other => panic!("{:?}", other), } @@ -472,7 +485,7 @@ mod tests { fn sign_up_then_send_validation_at_time() -> Result<()> { let connection = Connection::new_in_memory()?; let validation_token = - match connection.sign_up("paul@test.org", "12345")? { + match connection.sign_up("paul@atreides.com", "12345")? { SignUpResult::UserCreatedWaitingForValidation(token) => token, // Nominal case. other => panic!("{:?}", other), }; @@ -487,7 +500,7 @@ mod tests { fn sign_up_then_send_validation_too_late() -> Result<()> { let connection = Connection::new_in_memory()?; let validation_token = - match connection.sign_up_with_given_time("paul@test.org", "12345", Utc::now() - Duration::days(1))? { + match connection.sign_up_with_given_time("paul@atreides.com", "12345", Utc::now() - Duration::days(1))? { SignUpResult::UserCreatedWaitingForValidation(token) => token, // Nominal case. other => panic!("{:?}", other), }; @@ -502,7 +515,7 @@ mod tests { fn sign_up_then_send_validation_with_bad_token() -> Result<()> { let connection = Connection::new_in_memory()?; let _validation_token = - match connection.sign_up("paul@test.org", "12345")? { + match connection.sign_up("paul@atreides.com", "12345")? { SignUpResult::UserCreatedWaitingForValidation(token) => token, // Nominal case. other => panic!("{:?}", other), }; @@ -518,7 +531,7 @@ mod tests { fn sign_up_then_send_validation_then_sign_in() -> Result<()> { let connection = Connection::new_in_memory()?; - let email = "paul@test.org"; + let email = "paul@atreides.com"; let password = "12345"; // Sign up. @@ -547,7 +560,7 @@ mod tests { fn sign_up_then_send_validation_then_authentication() -> Result<()> { let connection = Connection::new_in_memory()?; - let email = "paul@test.org"; + let email = "paul@atreides.com"; let password = "12345"; // Sign up. @@ -587,7 +600,7 @@ mod tests { fn sign_up_then_send_validation_then_sign_out_then_sign_in() -> Result<()> { let connection = Connection::new_in_memory()?; - let email = "paul@test.org"; + let email = "paul@atreides.com"; let password = "12345"; // Sign up. diff --git a/backend/src/main.rs b/backend/src/main.rs index 03517f8..df7c44b 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -48,6 +48,7 @@ async fn main() -> std::io::Result<()> { .service(services::sign_in_post) .service(services::sign_out) .service(services::view_recipe) + .service(services::edit_recipe) .service(fs::Files::new("/static", "static")) .default_service(web::to(services::not_found)) }); diff --git a/backend/src/services.rs b/backend/src/services.rs index 2d69cc5..b4d39f9 100644 --- a/backend/src/services.rs +++ b/backend/src/services.rs @@ -94,6 +94,15 @@ impl From for ServiceError { } } +impl From for ServiceError { + fn from(error: ron::error::SpannedError) -> Self { + ServiceError { + status_code: StatusCode::INTERNAL_SERVER_ERROR, + message: Some(format!("{:?}", error)), + } + } +} + impl std::fmt::Display for ServiceError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { if let Some(ref m) = self.message { @@ -159,6 +168,32 @@ pub async fn view_recipe(req: HttpRequest, path: web::Path<(i64,)>, connection: }.to_response()) } +///// EDIT RECIPE ///// + +#[derive(Template)] +#[template(path = "edit_recipe.html")] +struct EditRecipeTemplate { + user: Option, + recipes: Vec<(i64, String)>, + current_recipe_id: Option, + current_recipe: model::Recipe, +} + +#[get("/recipe/edit/{id}")] +pub async fn edit_recipe(req: HttpRequest, path: web::Path<(i64,)>, connection: web::Data) -> Result { + let (id,)= path.into_inner(); + let user = get_current_user(&req, connection.clone()).await; + let recipes = connection.get_all_recipe_titles_async().await?; + let recipe = connection.get_recipe_async(id).await?; + + Ok(EditRecipeTemplate { + user, + current_recipe_id: Some(recipe.id), + recipes, + current_recipe: recipe, + }.to_response()) +} + ///// MESSAGE ///// #[derive(Template)] diff --git a/backend/src/services/api.rs b/backend/src/services/api.rs index aa160fc..fa126c5 100644 --- a/backend/src/services/api.rs +++ b/backend/src/services/api.rs @@ -1,6 +1,8 @@ -use actix_web::{http::{header, header::ContentType, StatusCode}, get, post, put, web, Responder, HttpRequest, HttpResponse, cookie::Cookie}; +use actix_web::{http::{header, header::ContentType, StatusCode}, get, post, put, web, Responder, HttpRequest, HttpResponse, cookie::Cookie, HttpMessage}; use chrono::Duration; +use futures::TryFutureExt; use serde::Deserialize; +use ron::de::from_bytes; use log::{debug, error, log_enabled, info, Level}; use super::Result; @@ -11,17 +13,16 @@ use crate::user::User; use crate::model; use crate::data::{db, asynchronous}; -#[put("/ron-api/set-title")] -pub async fn set_title(req: HttpRequest, connection: web::Data) -> Result { - //req.app_config() - let id = 1; - let title = "XYZ".to_string(); +#[put("/ron-api/recipe/set-title")] +pub async fn set_recipe_title(req: HttpRequest, body: web::Bytes, connection: web::Data) -> Result { + let ron_req: common::ron_api::SetRecipeTitle = from_bytes(&body)?; + connection.set_recipe_title_async(ron_req.recipe_id, &ron_req.title).await?; + Ok(HttpResponse::Ok().finish()) +} - //let recipes = connection.set_recipe_title_async(id, title).await?; - - Ok( - HttpResponse::Ok() - .content_type("application/ron") - .body("DATA") - ) +#[put("/ron-api/recipe/set-description")] +pub async fn set_recipe_description(req: HttpRequest, body: web::Bytes, connection: web::Data) -> Result { + let ron_req: common::ron_api::SetRecipeDescription = from_bytes(&body)?; + connection.set_recipe_description_async(ron_req.recipe_id, &ron_req.description).await?; + Ok(HttpResponse::Ok().finish()) } \ No newline at end of file diff --git a/backend/templates/edit_recipe.html b/backend/templates/edit_recipe.html new file mode 100644 index 0000000..81d9f2c --- /dev/null +++ b/backend/templates/edit_recipe.html @@ -0,0 +1,18 @@ +{% extends "base_with_list.html" %} + +{% block content %} + +

{{ current_recipe.title }}

+ + + + +{% match current_recipe.description %} + {% when Some with (description) %} +
+ {{ description|markdown }} +
+ {% when None %} +{% endmatch %} + +{% endblock %} \ No newline at end of file diff --git a/common/Cargo.toml b/common/Cargo.toml index 059cf02..84e1268 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -6,4 +6,7 @@ edition = "2021" [dependencies] regex = "1" -lazy_static = "1" \ No newline at end of file +lazy_static = "1" + +ron = "0.8" +serde = {version = "1.0", features = ["derive"]} \ No newline at end of file diff --git a/common/src/lib.rs b/common/src/lib.rs index fab870e..94cf1a5 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1 +1,2 @@ -pub mod utils; \ No newline at end of file +pub mod utils; +pub mod ron_api; \ No newline at end of file diff --git a/common/src/ron_api.rs b/common/src/ron_api.rs new file mode 100644 index 0000000..84b1619 --- /dev/null +++ b/common/src/ron_api.rs @@ -0,0 +1,14 @@ +use ron::de::from_reader; +use serde::Deserialize; + +#[derive(Deserialize, Clone)] +pub struct SetRecipeTitle { + pub recipe_id: i64, + pub title: String, +} + +#[derive(Deserialize, Clone)] +pub struct SetRecipeDescription { + pub recipe_id: i64, + pub description: String, +} diff --git a/frontend/Cargo.toml b/frontend/Cargo.toml index 374be95..ed5f4d1 100644 --- a/frontend/Cargo.toml +++ b/frontend/Cargo.toml @@ -14,18 +14,18 @@ default = ["console_error_panic_hook"] common = {path = "../common"} wasm-bindgen = "0.2" -web-sys = { version = "0.3", features = ['console', 'Document', 'Element', 'HtmlElement', 'Node', 'Window'] } +web-sys = {version = "0.3", features = ['console', 'Document', 'Element', 'HtmlElement', 'Node', 'Window', 'Location']} # The `console_error_panic_hook` crate provides better debugging of panics by # logging them with `console.error`. This is great for development, but requires # all the `std::fmt` and `std::panicking` infrastructure, so isn't great for # code size when deploying. -console_error_panic_hook = { version = "0.1", optional = true } +console_error_panic_hook = {version = "0.1", optional = true} # `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size # compared to the default allocator's ~10K. It is slower than the default # allocator, however. -wee_alloc = { version = "0.4", optional = true } +wee_alloc = {version = "0.4", optional = true} # [dev-dependencies] # wasm-bindgen-test = "0.3" diff --git a/frontend/src/handles.rs b/frontend/src/handles.rs new file mode 100644 index 0000000..87911ce --- /dev/null +++ b/frontend/src/handles.rs @@ -0,0 +1,6 @@ +use wasm_bindgen::prelude::*; +use web_sys::Document; + +pub fn edit_recipe(doc: &Document) { + +} \ No newline at end of file diff --git a/frontend/src/lib.rs b/frontend/src/lib.rs index 1607fc0..aea6567 100644 --- a/frontend/src/lib.rs +++ b/frontend/src/lib.rs @@ -1,4 +1,5 @@ mod utils; +mod handles; use wasm_bindgen::prelude::*; use web_sys::console; @@ -22,14 +23,41 @@ pub fn greet(name: &str) { #[wasm_bindgen(start)] pub fn main() -> Result<(), JsValue> { + console_error_panic_hook::set_once(); + let window = web_sys::window().expect("no global `window` exists"); let document = window.document().expect("should have a document on window"); - let body = document.body().expect("document should have a body"); + //let body = document.body().expect("document should have a body"); + + let location = window.location().pathname()?; + let path: Vec<&str> = location.split('/').skip(1).collect(); + + /* + * Todo: + * [ok] get url (/recipe/edit/{id}) and extract the id + * - Add a handle (event?) to the title field (when edited/changed?): + * - Call (as AJAR) /ron-api/set-title and set the body to a serialized RON of the type common::ron_api::SetTitle + * - Display error message if needed + */ + + match path[..] { + ["recipe", "edit", id] => { + let id = id.parse::().unwrap(); // TODO: remove unwrap. + console_log!("recipe edit ID: {}", id); + + handles::edit_recipe(&document); + + let title_input = document.get_element_by_id("title_field").unwrap(); + }, + _ => (), + } - let val = document.create_element("p")?; - val.set_inner_html("Hello from Rust!"); + //alert(&path); - body.append_child(&val)?; + // TEST + // let val = document.create_element("p")?; + // val.set_inner_html("Hello from Rust!"); + // body.append_child(&val)?; Ok(()) } \ No newline at end of file diff --git a/frontend/src/utils.rs b/frontend/src/utils.rs index b1d7929..88b2dec 100644 --- a/frontend/src/utils.rs +++ b/frontend/src/utils.rs @@ -1,3 +1,5 @@ +use web_sys::console; + pub fn set_panic_hook() { // When the `console_error_panic_hook` feature is enabled, we can call the // `set_panic_hook` function at least once during initialization, and then @@ -8,3 +10,10 @@ pub fn set_panic_hook() { #[cfg(feature = "console_error_panic_hook")] console_error_panic_hook::set_once(); } + +#[macro_export] +macro_rules! console_log { + // Note that this is using the `log` function imported above during + // `bare_bones` + ($($t:tt)*) => (console::log_1(&format_args!($($t)*).to_string().into())) +} \ No newline at end of file