diff --git a/backend/scss/main.scss b/backend/scss/main.scss index 0469b94..49a7d4f 100644 --- a/backend/scss/main.scss +++ b/backend/scss/main.scss @@ -251,10 +251,7 @@ body { input, button { - // background-color: rgb(52, 40, 85); border-width: 1px; - // border-color: white; - // color: white; } } diff --git a/backend/scss/mixins.scss b/backend/scss/mixins.scss index 4f429d8..2b6836b 100644 --- a/backend/scss/mixins.scss +++ b/backend/scss/mixins.scss @@ -1,3 +1,5 @@ +@use 'constants' as consts; + @mixin markdown { h1 { font-size: 140%; @@ -23,4 +25,15 @@ h6 { font-size: 100%; } +} + +// Common style for popup box like modal dialog and toast. +@mixin popup { + border: 0.1em solid consts.$color-3; + border-radius: 0.5em; + background-color: consts.$color-2; + + text-align: center; + padding: calc(2 * consts.$margin) calc(2 * consts.$margin); + box-shadow: -1px 1px 10px rgba(0, 0, 0, 0.3); } \ No newline at end of file diff --git a/backend/scss/modal-dialog.scss b/backend/scss/modal-dialog.scss index f21a3e6..c29dd14 100644 --- a/backend/scss/modal-dialog.scss +++ b/backend/scss/modal-dialog.scss @@ -1,19 +1,21 @@ +@use 'mixins'; +@use 'constants' as consts; + #modal-dialog { - // visibility: hidden; - color: white; - width: 800px; - margin-left: -400px; - background-color: black; - text-align: center; - border-radius: 2px; - padding: 16px; // TODO: 'rem' better? position: fixed; z-index: 1; left: 50%; - bottom: 30px; - box-shadow: -1px 1px 10px rgba(0, 0, 0, 0.3); -} + width: 50%; + transform: translate(-50%, 0%); -#modal-dialog.show { - visibility: visible; + @include mixins.popup; + + // The inner div will cover the entire dialog and prevent user to + // click on it to close the dialog. Thus, The use can click outside + // the div and it will be considered as a click on the dialog. + padding: 0; + + >div { + padding: calc(2 * consts.$margin) calc(2 * consts.$margin); + } } \ No newline at end of file diff --git a/backend/scss/toast.scss b/backend/scss/toast.scss index 0e1d1a9..9a8a6ed 100644 --- a/backend/scss/toast.scss +++ b/backend/scss/toast.scss @@ -1,4 +1,5 @@ @use 'constants' as consts; +@use 'mixins'; #toasts { position: fixed; @@ -11,18 +12,11 @@ flex-direction: column; .toast { - // visibility: hidden; display: none; width: fit-content; align-self: center; - border: 0.1em solid consts.$color-3; - border-radius: 0.5em; - background-color: consts.$color-2; - - text-align: center; - padding: calc(2 * consts.$margin) calc(2 * consts.$margin); - box-shadow: -1px 1px 10px rgba(0, 0, 0, 0.3); + @include mixins.popup; margin: consts.$margin; @@ -36,14 +30,6 @@ } } -// #toast.show { -// visibility: visible; -// animation: -// fadein 0.5s, -// fadeout 0.5s 9.5s; -// animation-iteration-count: 1; -// } - @keyframes fadein { from { opacity: 0; diff --git a/backend/templates/modal_dialog.html b/backend/templates/modal_dialog.html index 49578a9..d7066f7 100644 --- a/backend/templates/modal_dialog.html +++ b/backend/templates/modal_dialog.html @@ -1,6 +1,8 @@ {# Needed by the frontend modal_dialog module. #} -
- - +
+
+ + +
\ No newline at end of file diff --git a/frontend/src/modal_dialog.rs b/frontend/src/modal_dialog.rs index 01c499a..b81bed1 100644 --- a/frontend/src/modal_dialog.rs +++ b/frontend/src/modal_dialog.rs @@ -1,5 +1,5 @@ use futures::{future::FutureExt, pin_mut, select}; -use web_sys::{Element, HtmlDialogElement}; +use web_sys::{Element, HtmlDialogElement, Node}; use crate::{ on_click, @@ -25,20 +25,17 @@ where /// 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( +pub async fn show_and_initialize_with_ok( element_selector: &str, - initializer: T, - ok: V, -) -> Option + initializer: FInit, + ok: FOk, +) -> Option where - T: AsyncFn(Element) -> U, - V: Fn(Element, U) -> W, + FInit: AsyncFn(Element) -> TInit, + FOk: Fn(Element, TInit) -> TOk, { let dialog: HtmlDialogElement = by_id("modal-dialog"); - let input_ok: Element = dialog.selector(".ok"); - let input_cancel: Element = dialog.selector(".cancel"); - let content_element = dialog.selector::(".content"); let element: Element = selector_and_clone(element_selector); @@ -46,16 +43,25 @@ where content_element.append_child(&element).unwrap(); let init_result = initializer(element.clone()).await; + let input_ok: Element = dialog.selector(".ok"); + let input_cancel: Element = dialog.selector(".cancel"); + dialog.show_modal().unwrap(); let click_ok = on_click::OnClick::new(&input_ok).fuse(); let click_cancel = on_click::OnClick::new(&input_cancel).fuse(); - pin_mut!(click_ok, click_cancel); + let dialog_cloned = dialog.clone(); + let click_dialog = + on_click::OnClick::new_with_filter(&dialog, move |node| node == &dialog_cloned as &Node) + .fuse(); + + pin_mut!(click_ok, click_cancel, click_dialog); let result = select! { () = click_ok => Some(ok(element, init_result)), () = click_cancel => None, + () = click_dialog => None, }; dialog.close(); diff --git a/frontend/src/on_click.rs b/frontend/src/on_click.rs index 0c2b694..7a63190 100644 --- a/frontend/src/on_click.rs +++ b/frontend/src/on_click.rs @@ -7,7 +7,7 @@ use std::{ task::{Context, Poll}, }; use wasm_bindgen::prelude::*; -use web_sys::EventTarget; +use web_sys::{EventTarget, Node}; // From: https://docs.rs/gloo-events/latest/gloo_events/struct.EventListener.html @@ -19,11 +19,25 @@ pub struct OnClick { impl OnClick { pub fn new(target: &EventTarget) -> Self { + Self::new_with_filter(target, |_| true) + } + + pub fn new_with_filter(target: &EventTarget, filter: F) -> Self + where + F: Fn(&Node) -> bool + 'static, + { let (sender, receiver) = mpsc::unbounded(); - // Attach an event listener. - let listener = EventListener::new(target, "click", move |_event| { - sender.unbounded_send(()).unwrap_throw(); + let listener = EventListener::new(target, "click", move |event| { + let mut send = true; + if let Some(event_target) = event.target() { + if let Some(node) = event_target.dyn_ref::() { + send = filter(node); + } + } + if send { + sender.unbounded_send(()).unwrap_throw(); + } }); Self { receiver, listener } diff --git a/frontend/src/pages/dev_panel.rs b/frontend/src/pages/dev_panel.rs index ef42c1e..ac61419 100644 --- a/frontend/src/pages/dev_panel.rs +++ b/frontend/src/pages/dev_panel.rs @@ -1,4 +1,4 @@ -use gloo::{events::EventListener, utils::document}; +use gloo::{console::log, events::EventListener, utils::document}; use wasm_bindgen_futures::spawn_local; use crate::{ @@ -61,7 +61,11 @@ pub fn setup_page() { EventListener::new(&by_id("test-modal-dialog"), "click", move |_event| { spawn_local(async move { - modal_dialog::show("#hidden-templates .modal-test-message").await; + if modal_dialog::show("#hidden-templates .modal-test-message").await { + log!("Ok"); + } else { + log!("Cancel"); + } }); }) .forget(); diff --git a/frontend/src/utils.rs b/frontend/src/utils.rs index 3856111..3fbb3bf 100644 --- a/frontend/src/utils.rs +++ b/frontend/src/utils.rs @@ -49,6 +49,23 @@ impl SelectorExt for Element { } } +// Not used anymore. +// pub trait NodeHelperExt { +// fn is_parent(&self, child: &Node) -> bool; +// } +// impl NodeHelperExt for Node { +// fn is_parent(&self, child: &Node) -> bool { +// let mut node = child.clone(); +// while let Some(parent) = node.parent_node() { +// if &parent == self { +// return true; +// } +// node = parent; +// } +// false +// } +// } + pub fn selector(selectors: &str) -> T where T: JsCast,