diff --git a/ui/media/css/main.css b/ui/media/css/main.css index 08dea664..ba513237 100644 --- a/ui/media/css/main.css +++ b/ui/media/css/main.css @@ -1302,3 +1302,54 @@ body.wait-pause { .displayNone { display:none !important; } + +/* TOAST NOTIFICATIONS */ +.toast-notification { + position: fixed; + bottom: 10px; + right: -300px; + width: 300px; + background-color: #333; + color: #fff; + padding: 10px 20px; + border-radius: 5px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); + z-index: 9999; + animation: slideInRight 0.5s ease forwards; + transition: bottom 0.5s ease; // Add a transition to smoothly reposition the toasts +} + +.toast-notification-error { + color: red; +} + +@keyframes slideInRight { + from { + right: -300px; + } + to { + right: 10px; + } +} + +.toast-notification.hide { + animation: slideOutRight 0.5s ease forwards; +} + +@keyframes slideOutRight { + from { + right: 10px; + } + to { + right: -300px; + } +} + +@keyframes slideDown { + from { + bottom: 10px; + } + to { + bottom: 0; + } +} diff --git a/ui/media/js/utils.js b/ui/media/js/utils.js index 6558f7d2..d1578d8e 100644 --- a/ui/media/js/utils.js +++ b/ui/media/js/utils.js @@ -840,3 +840,60 @@ function createTab(request) { } }) } + +/* TOAST NOTIFICATIONS */ +function showToast(message, duration = 5000, error = false) { + const toast = document.createElement("div"); + toast.classList.add("toast-notification"); + if (error === true) { + toast.classList.add("toast-notification-error"); + } + toast.innerHTML = message; + document.body.appendChild(toast); + + // Set the position of the toast on the screen + const toastCount = document.querySelectorAll(".toast-notification").length; + const toastHeight = toast.offsetHeight; + const previousToastsHeight = Array.from(document.querySelectorAll(".toast-notification")) + .slice(0, -1) // exclude current toast + .reduce((totalHeight, toast) => totalHeight + toast.offsetHeight + 10, 0); // add 10 pixels for spacing + toast.style.bottom = `${10 + previousToastsHeight}px`; + toast.style.right = "10px"; + + // Delay the removal of the toast until animation has completed + const removeToast = () => { + toast.classList.add("hide"); + const removeTimeoutId = setTimeout(() => { + toast.remove(); + // Adjust the position of remaining toasts + const remainingToasts = document.querySelectorAll(".toast-notification"); + const removedToastBottom = toast.getBoundingClientRect().bottom; + + remainingToasts.forEach((toast) => { + if (toast.getBoundingClientRect().bottom < removedToastBottom) { + toast.classList.add("slide-down"); + } + }); + + // Wait for the slide-down animation to complete + setTimeout(() => { + // Remove the slide-down class after the animation has completed + const slidingToasts = document.querySelectorAll(".slide-down"); + slidingToasts.forEach((toast) => { + toast.classList.remove("slide-down"); + }); + + // Adjust the position of remaining toasts again, in case there are multiple toasts being removed at once + const remainingToastsDown = document.querySelectorAll(".toast-notification"); + let heightSoFar = 0; + remainingToastsDown.forEach((toast) => { + toast.style.bottom = `${10 + heightSoFar}px`; + heightSoFar += toast.offsetHeight + 10; // add 10 pixels for spacing + }); + }, 0); // The duration of the slide-down animation (in milliseconds) + }, 500); + }; + + // Remove the toast after specified duration + setTimeout(removeToast, duration); +}