Refactor toast notifications and modal dialog implementation
- Updated SCSS for toast notifications to support multiple toast types (success, info, warning, error) and improved layout. - Added new SVG icons for error, info, success, and warning notifications. - Created separate HTML templates for toast notifications and modal dialogs. - Enhanced the dev panel with buttons to test different toast notifications and modal dialogs.
This commit is contained in:
parent
7b9df97a32
commit
cf9c6b2a3f
15 changed files with 399 additions and 70 deletions
|
|
@ -44,6 +44,7 @@ web-sys = { version = "0.3", features = [
|
|||
"HtmlDivElement",
|
||||
"HtmlLabelElement",
|
||||
"HtmlInputElement",
|
||||
"HtmlImageElement",
|
||||
"HtmlTextAreaElement",
|
||||
"HtmlSelectElement",
|
||||
"HtmlDialogElement",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use gloo::{console::log, events::EventListener};
|
||||
use gloo::{console::log, events::EventListener, utils::document};
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
use web_sys::{Element, HtmlInputElement};
|
||||
use web_sys::{Element, HtmlElement, HtmlInputElement};
|
||||
|
||||
use crate::{
|
||||
calendar, modal_dialog,
|
||||
|
|
@ -13,10 +13,56 @@ use crate::{
|
|||
|
||||
pub fn setup_page() {
|
||||
EventListener::new(&by_id("test-toast"), "click", move |_event| {
|
||||
toast::show_message(Level::Info, "This is a message");
|
||||
toast::show_message("This is a message");
|
||||
})
|
||||
.forget();
|
||||
|
||||
EventListener::new(&by_id("test-toast-success"), "click", move |_event| {
|
||||
toast::show_message_level(Level::Success, "This is a success message");
|
||||
})
|
||||
.forget();
|
||||
|
||||
EventListener::new(&by_id("test-toast-error"), "click", move |_event| {
|
||||
toast::show_message_level(Level::Error, "This is a error message");
|
||||
})
|
||||
.forget();
|
||||
|
||||
EventListener::new(&by_id("test-toast-info"), "click", move |_event| {
|
||||
toast::show_message_level(Level::Info, "This is an info message");
|
||||
})
|
||||
.forget();
|
||||
|
||||
EventListener::new(&by_id("test-toast-warning"), "click", move |_event| {
|
||||
toast::show_message_level(Level::Warning, "This is a warning message");
|
||||
})
|
||||
.forget();
|
||||
|
||||
EventListener::new(
|
||||
&by_id("test-toast-success-content"),
|
||||
"click",
|
||||
move |_event| {
|
||||
toast::show_element_level(Level::Success, "#hidden-templates .toast-test-content");
|
||||
},
|
||||
)
|
||||
.forget();
|
||||
|
||||
EventListener::new(
|
||||
&by_id("test-toast-success-content-initializer"),
|
||||
"click",
|
||||
move |_event| {
|
||||
toast::show_element_level_and_initialize(
|
||||
Level::Success,
|
||||
"#hidden-templates .toast-test-content",
|
||||
|element| {
|
||||
let new_span = document().create_element("span").unwrap();
|
||||
new_span.set_inner_html("Item 3");
|
||||
element.append_child(&new_span).unwrap();
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
.forget();
|
||||
|
||||
EventListener::new(&by_id("test-modal-dialog"), "click", move |_event| {
|
||||
spawn_local(async move {
|
||||
modal_dialog::show("#hidden-templates").await;
|
||||
|
|
|
|||
|
|
@ -76,7 +76,6 @@ pub fn setup_page(recipe_id: i64) {
|
|||
let servings = if n.is_nan() {
|
||||
None
|
||||
} else {
|
||||
// TODO: Find a better way to validate integer numbers.
|
||||
let n = n as u32;
|
||||
servings.set_value_as_number(n as f64);
|
||||
Some(n)
|
||||
|
|
@ -108,7 +107,6 @@ pub fn setup_page(recipe_id: i64) {
|
|||
let time = if n.is_nan() {
|
||||
None
|
||||
} else {
|
||||
// TODO: Find a better way to validate integer numbers.
|
||||
let n = n as u32;
|
||||
estimated_time.set_value_as_number(n as f64);
|
||||
Some(n)
|
||||
|
|
@ -708,7 +706,7 @@ async fn reload_recipes_list(current_recipe_id: i64) {
|
|||
.await
|
||||
{
|
||||
Err(error) => {
|
||||
toast::show_message(Level::Info, &format!("Internal server error: {}", error));
|
||||
toast::show_message_level(Level::Error, &format!("Internal server error: {}", error));
|
||||
}
|
||||
Ok(response) => {
|
||||
let list = document().get_element_by_id("recipes-list").unwrap();
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ pub fn setup_page(recipe_id: i64, is_user_logged: bool, first_day_of_the_week: W
|
|||
.shedule_recipe(recipe_id, date, servings, add_ingredients_to_shopping_list)
|
||||
.await
|
||||
{
|
||||
toast::show_element_and_initialize(
|
||||
toast::show_element_level_and_initialize(
|
||||
match result {
|
||||
ScheduleRecipeResult::Ok => Level::Success,
|
||||
ScheduleRecipeResult::RecipeAlreadyScheduledAtThisDate => {
|
||||
|
|
|
|||
|
|
@ -64,13 +64,13 @@ where
|
|||
{
|
||||
match request.send().await {
|
||||
Err(error) => {
|
||||
toast::show_message(Level::Info, &format!("Internal server error: {}", error));
|
||||
toast::show_message_level(Level::Error, &format!("Internal server error: {}", error));
|
||||
Err(Error::Gloo(error))
|
||||
}
|
||||
Ok(response) => {
|
||||
if !response.ok() {
|
||||
toast::show_message(
|
||||
Level::Info,
|
||||
toast::show_message_level(
|
||||
Level::Error,
|
||||
&format!("HTTP error: {}", response.status_text()),
|
||||
);
|
||||
Err(Error::Http(response.status_text()))
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use gloo::{timers::callback::Timeout, utils::document};
|
||||
use web_sys::{Element, HtmlElement};
|
||||
use gloo::{events::EventListener, timers::callback::Timeout};
|
||||
use web_sys::{Element, HtmlElement, HtmlImageElement};
|
||||
|
||||
use crate::utils::{SelectorExt, by_id, selector_and_clone};
|
||||
|
||||
|
|
@ -8,55 +8,82 @@ pub enum Level {
|
|||
Error,
|
||||
Info,
|
||||
Warning,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
/*
|
||||
TODO:
|
||||
- Stack multiple toast messages (see #toasts) by cloning #toast
|
||||
- User can close message by clicking a button
|
||||
- Implement level display with icons
|
||||
*/
|
||||
|
||||
const TIME_ANIMATION: u32 = 500; // [ms].
|
||||
const TIME_DISPLAYED: u32 = 5_000; // [ms].
|
||||
|
||||
pub fn show_message(level: Level, message: &str) {
|
||||
let toast_element: HtmlElement = by_id("toast");
|
||||
toast_element.set_inner_html(message);
|
||||
toast_element.style().set_css_text(&format!(
|
||||
"visibility: visible;
|
||||
animation:
|
||||
fadein {}ms,
|
||||
fadeout {}ms {}ms;",
|
||||
TIME_ANIMATION,
|
||||
TIME_ANIMATION,
|
||||
TIME_DISPLAYED - TIME_ANIMATION
|
||||
));
|
||||
|
||||
Timeout::new(TIME_DISPLAYED, move || {
|
||||
toast_element.style().set_css_text("");
|
||||
})
|
||||
.forget();
|
||||
pub fn show_message(message: &str) {
|
||||
show_message_level(Level::Unknown, message);
|
||||
}
|
||||
|
||||
pub fn show_element(level: Level, selector: &str) {
|
||||
show_element_and_initialize(level, selector, |_| {})
|
||||
pub fn show_message_level(level: Level, message: &str) {
|
||||
show_message_content(level, Content::Message(message))
|
||||
}
|
||||
|
||||
pub fn show_element_and_initialize<T>(level: Level, selector: &str, initializer: T)
|
||||
pub fn show_element_level(level: Level, selector: &str) {
|
||||
show_element_level_and_initialize(level, selector, |_| {})
|
||||
}
|
||||
|
||||
pub fn show_element_level_and_initialize<T>(level: Level, selector: &str, initializer: T)
|
||||
where
|
||||
T: Fn(Element),
|
||||
{
|
||||
let toast_element = document().get_element_by_id("toast").unwrap();
|
||||
let content_element: Element = selector_and_clone(selector);
|
||||
initializer(content_element.clone());
|
||||
|
||||
let element: Element = selector_and_clone(selector);
|
||||
toast_element.set_inner_html("");
|
||||
toast_element.append_child(&element).unwrap();
|
||||
initializer(element.clone());
|
||||
toast_element.set_class_name("show");
|
||||
show_message_content(level, Content::Element(content_element))
|
||||
}
|
||||
|
||||
enum Content<'a> {
|
||||
Message(&'a str),
|
||||
Element(Element),
|
||||
}
|
||||
|
||||
fn show_message_content(level: Level, content: Content) {
|
||||
let toast_element: HtmlElement = selector_and_clone("#toasts .toast");
|
||||
|
||||
let toast_icon: HtmlImageElement = toast_element.selector(".icon");
|
||||
let toast_content: HtmlElement = toast_element.selector(".content");
|
||||
|
||||
match level {
|
||||
Level::Success => toast_icon.set_src("/static/success.svg"),
|
||||
Level::Error => toast_icon.set_src("/static/error.svg"),
|
||||
Level::Info => toast_icon.set_src("/static/info.svg"),
|
||||
Level::Warning => toast_icon.set_src("/static/warning.svg"),
|
||||
Level::Unknown => toast_icon.remove(),
|
||||
}
|
||||
|
||||
match content {
|
||||
Content::Message(message) => toast_content.set_inner_html(message),
|
||||
Content::Element(element) => {
|
||||
let _ = toast_content.append_child(&element);
|
||||
}
|
||||
}
|
||||
|
||||
toast_element.style().set_css_text(&format!(
|
||||
"display: block;
|
||||
animation:
|
||||
fadein {}ms,
|
||||
fadeout {}ms {}ms;",
|
||||
TIME_ANIMATION, TIME_ANIMATION, TIME_DISPLAYED
|
||||
));
|
||||
|
||||
// FIXME: Here the two events will leak memory. How to fix that?
|
||||
// Save them in a global vec variable and remove them manually?
|
||||
let close_button: HtmlElement = toast_element.selector(".close");
|
||||
let toast_element_cloned = toast_element.clone();
|
||||
EventListener::once(&close_button, "click", move |_event| {
|
||||
toast_element_cloned.remove();
|
||||
})
|
||||
.forget();
|
||||
|
||||
let toasts: HtmlElement = by_id("toasts");
|
||||
toasts.append_child(&toast_element).unwrap();
|
||||
|
||||
Timeout::new(TIME_DISPLAYED, move || {
|
||||
toast_element.set_class_name("");
|
||||
toast_element.remove();
|
||||
})
|
||||
.forget();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue