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
|
|
@ -1,21 +1,39 @@
|
|||
@use 'constants' as consts;
|
||||
|
||||
#toast {
|
||||
visibility: hidden;
|
||||
width: 300px;
|
||||
margin-left: -125px;
|
||||
|
||||
border: 0.1em solid consts.$color-3;
|
||||
border-radius: 0.5em;
|
||||
background-color: consts.$color-2;
|
||||
|
||||
text-align: center;
|
||||
padding: consts.$margin;
|
||||
#toasts {
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
left: 50%;
|
||||
top: 30px;
|
||||
box-shadow: -1px 1px 10px rgba(0, 0, 0, 0.3);
|
||||
top: 15px;
|
||||
transform: translate(-50%, 0%);
|
||||
|
||||
display: flex;
|
||||
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(4 * consts.$margin);
|
||||
box-shadow: -1px 1px 10px rgba(0, 0, 0, 0.3);
|
||||
|
||||
margin: consts.$margin;
|
||||
|
||||
.content {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
img {
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// #toast.show {
|
||||
|
|
|
|||
54
backend/static/error.svg
Normal file
54
backend/static/error.svg
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="40.905399mm"
|
||||
height="40.905399mm"
|
||||
viewBox="0 0 40.905399 40.905399"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="error.svg"
|
||||
inkscape:version="1.4 (86a8ad7, 2024-10-11)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="true"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:zoom="1.4702524"
|
||||
inkscape:cx="-96.582055"
|
||||
inkscape:cy="13.263029"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1369"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="828"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1" />
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-80.178404,-120.49168)">
|
||||
<circle
|
||||
style="fill:#ff1e1e;fill-opacity:1;stroke:#a00000;stroke-width:7;stroke-linecap:butt;stroke-linejoin:bevel;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
id="path2"
|
||||
cx="100.6311"
|
||||
cy="140.94438"
|
||||
r="16.9527" />
|
||||
<path
|
||||
d="m 107.32373,131.05185 -6.61942,6.62988 q -0.0732,0.0628 -0.13595,0 l -6.629879,-6.62988 q -0.658805,-0.6588 -1.599955,-0.6588 -0.930694,0 -1.589499,0.6588 -0.669262,0.66926 -0.669262,1.61042 0,0.93069 0.669262,1.58949 l 6.619425,6.62989 q 0.0732,0.0627 0,0.12548 l -6.619425,6.62988 q -0.669262,0.65881 -0.669262,1.5895 0,0.94115 0.669262,1.61042 0.658805,0.6588 1.589499,0.6588 0.94115,0 1.599955,-0.6588 l 6.629879,-6.62989 q 0.0628,-0.0627 0.13595,0 l 6.61942,6.62989 q 0.65881,0.6588 1.59996,0.6588 0.94115,0 1.59995,-0.6588 0.65881,-0.66927 0.65881,-1.61042 0,-0.93069 -0.65881,-1.5895 l -6.62988,-6.62988 q -0.0627,-0.0627 0,-0.12548 l 6.62988,-6.62989 q 0.65881,-0.6588 0.65881,-1.58949 0,-0.94116 -0.65881,-1.61042 -0.6588,-0.6588 -1.59995,-0.6588 -0.94115,0 -1.59996,0.6588 z"
|
||||
id="text1"
|
||||
style="font-size:21.4164px;font-family:'Arial Rounded MT Bold';-inkscape-font-specification:'Arial Rounded MT Bold, ';fill:#ffffff;stroke-width:8.36709;stroke-linejoin:round;paint-order:stroke fill markers"
|
||||
aria-label="❌" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
54
backend/static/info.svg
Normal file
54
backend/static/info.svg
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="40.905399mm"
|
||||
height="40.905399mm"
|
||||
viewBox="0 0 40.905399 40.905399"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="info.svg"
|
||||
inkscape:version="1.4 (86a8ad7, 2024-10-11)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="true"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:zoom="1.4702524"
|
||||
inkscape:cx="-31.287145"
|
||||
inkscape:cy="44.550174"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1369"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="828"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1" />
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-80.178404,-120.49168)">
|
||||
<circle
|
||||
style="fill:#1e22ff;fill-opacity:1;stroke:#0003a0;stroke-width:7;stroke-linecap:butt;stroke-linejoin:bevel;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
id="path2"
|
||||
cx="100.6311"
|
||||
cy="140.94438"
|
||||
r="16.9527" />
|
||||
<path
|
||||
d="m 103.30394,136.96938 v 15.44961 q 0,1.60566 -0.76367,2.42808 -0.76367,0.82241 -1.93854,0.82241 -1.174876,0 -1.918964,-0.842 -0.724506,-0.84199 -0.724506,-2.40849 v -15.29296 q 0,-1.58608 0.724506,-2.38891 0.744088,-0.80283 1.918964,-0.80283 1.17487,0 1.93854,0.80283 0.76367,0.80283 0.76367,2.23226 z m -2.64347,-5.52191 q -1.116129,0 -1.91896,-0.68534 -0.78325,-0.68535 -0.78325,-1.93855 0,-1.13571 0.802831,-1.86022 0.822413,-0.74408 1.899379,-0.74408 1.03781,0 1.84064,0.66576 0.80283,0.66576 0.80283,1.93854 0,1.23362 -0.78325,1.93855 -0.78325,0.68534 -1.86022,0.68534 z"
|
||||
id="text1"
|
||||
style="font-size:40.1024px;font-family:'Arial Rounded MT Bold';-inkscape-font-specification:'Arial Rounded MT Bold, ';fill:#ffffff;stroke-width:19.8934;stroke-linejoin:round;paint-order:stroke fill markers"
|
||||
aria-label="i" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
54
backend/static/success.svg
Normal file
54
backend/static/success.svg
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="40.905399mm"
|
||||
height="40.905399mm"
|
||||
viewBox="0 0 40.905399 40.905399"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="success.svg"
|
||||
inkscape:version="1.4 (86a8ad7, 2024-10-11)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="true"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:zoom="0.73512621"
|
||||
inkscape:cx="-352.32046"
|
||||
inkscape:cy="-74.13693"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1369"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="828"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1" />
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-80.178404,-120.49168)">
|
||||
<circle
|
||||
style="fill:#03b500;fill-opacity:1;stroke:#027500;stroke-width:7;stroke-linecap:butt;stroke-linejoin:bevel;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
id="path2"
|
||||
cx="100.6311"
|
||||
cy="140.94438"
|
||||
r="16.9527" />
|
||||
<path
|
||||
d="m 112.46328,132.63911 q -0.58288,-0.62262 -1.37771,-0.84782 -0.78159,-0.2252 -1.58967,-0.0397 -0.79483,0.17221 -1.41745,0.75509 l -11.962235,11.27338 -3.85494,-3.96092 q -0.596125,-0.60937 -1.390958,-0.82132 -0.781586,-0.21196 -1.576419,-0.0132 -0.794833,0.18546 -1.417452,0.78158 -0.609372,0.59613 -0.821328,1.39096 -0.211955,0.79484 -0.02649,1.58967 0.198708,0.79483 0.794833,1.4042 l 5.974495,6.14671 q 0.887563,0.91406 2.159296,0.94055 1.271733,0.0265 2.199038,-0.84782 l 14.174524,-13.36644 q 0.62262,-0.58288 0.84782,-1.36446 0.2252,-0.79484 0.0397,-1.58967 -0.17221,-0.80808 -0.75509,-1.4307 z"
|
||||
id="text1"
|
||||
style="font-size:27.1303px;font-family:'Arial Rounded MT Bold';-inkscape-font-specification:'Arial Rounded MT Bold, ';fill:#ffffff;stroke-width:13.4584;stroke-linejoin:round;paint-order:stroke fill markers"
|
||||
aria-label="✔️" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
53
backend/static/warning.svg
Normal file
53
backend/static/warning.svg
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="40.905399mm"
|
||||
height="40.905399mm"
|
||||
viewBox="0 0 40.905399 40.905399"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="warning.svg"
|
||||
inkscape:version="1.4 (86a8ad7, 2024-10-11)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="true"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:zoom="1.0396254"
|
||||
inkscape:cx="-80.79833"
|
||||
inkscape:cy="65.889115"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1369"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="828"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1" />
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-80.178404,-120.49168)">
|
||||
<path
|
||||
style="fill:#ffff1a;fill-opacity:1;stroke:#616100;stroke-width:5.513;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
d="m 84.523721,157.05141 16.107569,-32.21437 16.10679,32.21437 z"
|
||||
id="path1"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
style="font-size:37.4875px;font-family:'Arial Rounded MT Bold';-inkscape-font-specification:'Arial Rounded MT Bold, ';fill:#000000;fill-opacity:1;stroke-width:2.49752"
|
||||
d="m 98.737968,144.66325 -0.550671,-8.24286 q -0.154876,-2.40918 -0.154876,-3.4589 0,-1.4283 0.739965,-2.2199 0.757173,-0.80879 1.978984,-0.80879 1.47992,0 1.97897,1.03251 0.49904,1.01529 0.49904,2.94265 0,1.13576 -0.12046,2.30593 l -0.73996,8.48378 q -0.12047,1.51435 -0.51626,2.32314 -0.39579,0.8088 -1.30784,0.8088 -0.929259,0 -1.290638,-0.77438 -0.361377,-0.79159 -0.516254,-2.39198 z m 1.910142,11.32318 q -1.049718,0 -1.841307,-0.67113 -0.774382,-0.68834 -0.774382,-1.91015 0,-1.06692 0.739965,-1.80688 0.757173,-0.75717 1.841304,-0.75717 1.08413,0 1.84131,0.75717 0.77438,0.73996 0.77438,1.80688 0,1.2046 -0.77438,1.89294 -0.77438,0.68834 -1.80689,0.68834 z"
|
||||
id="text1"
|
||||
aria-label="!" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
|
|
@ -23,15 +23,8 @@
|
|||
dispatchEvent(new CustomEvent("TrunkApplicationStarted", {detail: {wasm}}));
|
||||
</script>
|
||||
|
||||
<div id="toasts">
|
||||
<div id="toast"></div>
|
||||
</div>
|
||||
|
||||
<dialog id="modal-dialog">
|
||||
<div class="content"></div>
|
||||
<input type="button" class="ok" value="OK">
|
||||
<input type="button" class="cancel" value="Cancel">
|
||||
</dialog>
|
||||
{% include "toast.html" %}
|
||||
{% include "modal_dialog.html" %}
|
||||
|
||||
{% block body_container %}{% endblock %}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,14 +3,31 @@
|
|||
{% block content %}
|
||||
|
||||
<div class="content" id="dev-panel">
|
||||
<input type="button" class="button" id="test-toast" value="Test toast">
|
||||
<input type="button" class="button" id="test-modal-dialog" value="Test modal">
|
||||
<div>
|
||||
<input type="button" class="button" id="test-toast" value="Test toast">
|
||||
<input type="button" class="button" id="test-toast-success" value="Test toast success">
|
||||
<input type="button" class="button" id="test-toast-info" value="Test toast info">
|
||||
<input type="button" class="button" id="test-toast-warning" value="Test toast warning">
|
||||
<input type="button" class="button" id="test-toast-error" value="Test toast error">
|
||||
|
||||
<input type="button" class="button" id="test-toast-success-content" value="Test toast content">
|
||||
<input type="button" class="button" id="test-toast-success-content-initializer" value="Test toast content initializer">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input type="button" class="button" id="test-modal-dialog" value="Test modal">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="hidden-templates">
|
||||
<div class="modal-test-message">
|
||||
This is a message.
|
||||
</div>
|
||||
|
||||
<div class="toast-test-content">
|
||||
<span>Item 1</span>
|
||||
<span>Item 2</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
6
backend/templates/modal_dialog.html
Normal file
6
backend/templates/modal_dialog.html
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{# Needed by the frontend modal_dialog module. #}
|
||||
<dialog id="modal-dialog">
|
||||
<div class="content"></div>
|
||||
<input type="button" class="ok" value="OK">
|
||||
<input type="button" class="cancel" value="Cancel">
|
||||
</dialog>
|
||||
8
backend/templates/toast.html
Normal file
8
backend/templates/toast.html
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{# Needed by the frontend toast module. #}
|
||||
<div id="toasts">
|
||||
<div class="toast">
|
||||
<img class="icon" width="24" height="24">
|
||||
<div class="content"></div>
|
||||
<span class="close button">✖</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -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