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:
Greg Burri 2025-04-27 12:49:39 +02:00
parent 7b9df97a32
commit cf9c6b2a3f
15 changed files with 399 additions and 70 deletions

View file

@ -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();
}