fix: toast handling, mobile breakpoint
`toast.css` and `index.js` - fixed toasts disappearing right after showing - simplified toast animation `index.css` - set 2ⁿᵈ mobile breakpoint at 640px
Alessandro committed
Dec 4, 2024 at 19:37 UTC
7c2866ca614fff704b820e8f1ff6c9f50006320b
3 files changed
+97
-51
webui/css/toast.css
+11
-4
@@ -9,8 +9,8 @@
9
display: none;
10
align-items: center;
11
z-index: 1000;
12
- transform: translateY(100%) scale(0.98);
13
- transition: transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
12
+ transform: translateY(100%);
13
+ transition: transform 0.4s cubic-bezier(0.19, 0.86, 0.47, 1), background-color 0.3s ease;
14
will-change: transform;
15
}
16
@@ -43,11 +43,11 @@
43
44
#toast.show {
45
display: flex;
46
- transform: translateY(0) scale(1);
46
+ transform: translateY(0);
47
}
48
49
#toast.hide {
50
- transform: translateY(100%) scale(0.98);
50
+ transform: translateY(100%);
51
}
52
53
#toast.toast--success {
@@ -71,6 +71,13 @@
71
cursor: pointer;
72
font-size: 16px;
73
margin-left: 8px;
74
+ opacity: 0.8;
75
+ transition: opacity 0.2s ease;
76
+}
77
+
78
+.toast__close:hover,
79
+.toast__copy:hover {
80
+ opacity: 1;
81
}
82
83
/* Animations */
webui/index.css
+2
-2
@@ -1535,7 +1535,7 @@ input:checked + .slider:before {
1535
}
1536
1537
/* Media Queries */
1538
-@media (max-width: 510px) {
1538
+@media (max-width: 640px) {
1539
.text-buttons-row {
1540
display: table;
1541
gap: 0.1rem !important;
@@ -1590,7 +1590,7 @@ input:checked + .slider:before {
1590
}
1591
}
1592
1593
-@media (max-width: 600px) {
1593
+@media (max-width: 640px) {
1594
#chat-input {
1595
min-height: 5.3rem;
1596
align-content: start;
webui/index.js
+84
-45
@@ -499,22 +499,35 @@ window.nudge = async function () {
499
500
window.restart = async function () {
501
try {
502
+ // First try to initiate restart
503
const resp = await sendJsonData("/restart", {});
504
} catch (e) {
504
- //error expected here
505
- toast("Restarting...", "info", 0)
506
- while (true) {
505
+ // Show restarting message
506
+ toast("Restarting...", "info", 0);
507
+
508
+ let retries = 0;
509
+ const maxRetries = 60; // Maximum number of retries (15 seconds with 250ms interval)
510
+
511
+ while (retries < maxRetries) {
512
try {
508
- // try health check until server is back up again
513
const resp = await sendJsonData("/health", {});
514
+ // Server is back up, show success message
515
await new Promise(resolve => setTimeout(resolve, 250));
511
- toast("Restarted", "success", 2000)
516
+ hideToast();
517
+ await new Promise(resolve => setTimeout(resolve, 400));
518
+ toast("Restarted", "success", 5000);
519
return;
520
} catch (e) {
514
- continue;
521
+ // Server still down, keep waiting
522
+ retries++;
523
+ await new Promise(resolve => setTimeout(resolve, 250));
524
}
525
}
517
-
526
+
527
+ // If we get here, restart failed or took too long
528
+ hideToast();
529
+ await new Promise(resolve => setTimeout(resolve, 400));
530
+ toast("Restart timed out or failed", "error", 5000);
531
}
532
}
533
@@ -681,66 +694,92 @@ function removeClassFromElement(element, className) {
694
695
function toast(text, type = 'info', timeout = 5000) {
696
const toast = document.getElementById('toast');
684
- const isVisible = toast.style.display === 'flex';
697
+ const isVisible = toast.classList.contains('show');
698
686
- // Update the toast content and type
687
- const title = type.charAt(0).toUpperCase() + type.slice(1);
688
- toast.querySelector('.toast__title').textContent = title;
689
- toast.querySelector('.toast__message').textContent = text;
690
- toast.className = `toast toast--${type}`;
699
+ // Clear any existing timeout immediately
700
+ if (toast.timeoutId) {
701
+ clearTimeout(toast.timeoutId);
702
+ toast.timeoutId = null;
703
+ }
704
692
- // Show/hide copy button based on toast type
693
- const copyButton = toast.querySelector('.toast__copy');
694
- copyButton.style.display = type === 'error' ? 'inline-block' : 'none';
705
+ // Function to update toast content and show it
706
+ const updateAndShowToast = () => {
707
+ // Update the toast content and type
708
+ const title = type.charAt(0).toUpperCase() + type.slice(1);
709
+ toast.querySelector('.toast__title').textContent = title;
710
+ toast.querySelector('.toast__message').textContent = text;
711
696
- // Add the close button event listener
697
- const closeButton = toast.querySelector('.toast__close');
698
- closeButton.onclick = () => {
699
- hideToast();
700
- };
712
+ // Remove old classes and add new ones
713
+ toast.classList.remove('toast--success', 'toast--error', 'toast--info');
714
+ toast.classList.add(`toast--${type}`);
715
702
- // Add the copy button event listener
703
- copyButton.onclick = () => {
704
- navigator.clipboard.writeText(text);
705
- copyButton.textContent = 'Copied!';
706
- setTimeout(() => {
707
- copyButton.textContent = 'Copy';
708
- }, 2000);
709
- };
716
+ // Show/hide copy button based on toast type
717
+ const copyButton = toast.querySelector('.toast__copy');
718
+ copyButton.style.display = type === 'error' ? 'inline-block' : 'none';
719
+
720
+ // Add the close button event listener
721
+ const closeButton = document.querySelector('.toast__close');
722
+ closeButton.onclick = () => {
723
+ hideToast();
724
+ };
725
711
- // Only trigger animation if toast is not already visible
712
- if (!isVisible) {
713
- // Remove any existing animation classes
714
- toast.classList.remove('show', 'hide');
726
+ // Add the copy button event listener
727
+ copyButton.onclick = () => {
728
+ navigator.clipboard.writeText(text);
729
+ copyButton.textContent = 'Copied!';
730
+ setTimeout(() => {
731
+ copyButton.textContent = 'Copy';
732
+ }, 2000);
733
+ };
734
716
- // Show the toast and trigger animation
735
+ // Show the toast
736
toast.style.display = 'flex';
737
// Force a reflow to ensure the animation triggers
738
void toast.offsetWidth;
739
toast.classList.add('show');
721
- }
740
723
- // Clear any existing timeout
724
- if (toast.timeoutId)
725
- clearTimeout(toast.timeoutId);
741
+ // Set timeout if specified
742
+ if (timeout) {
743
+ const minTimeout = Math.max(timeout, 5000);
744
+ toast.timeoutId = setTimeout(() => {
745
+ hideToast();
746
+ }, minTimeout);
747
+ }
748
+ };
749
727
- // Automatically close the toast after specified timeout
728
- if (timeout)
729
- toast.timeoutId = setTimeout(() => {
730
- hideToast();
731
- }, timeout);
750
+ if (isVisible) {
751
+ // If a toast is visible, hide it first then show the new one
752
+ toast.classList.remove('show');
753
+ toast.classList.add('hide');
754
+
755
+ // Wait for hide animation to complete before showing new toast
756
+ setTimeout(() => {
757
+ toast.classList.remove('hide');
758
+ updateAndShowToast();
759
+ }, 400); // Match this with CSS transition duration
760
+ } else {
761
+ // If no toast is visible, show the new one immediately
762
+ updateAndShowToast();
763
+ }
764
}
765
766
function hideToast() {
767
const toast = document.getElementById('toast');
768
+
769
+ // Clear any existing timeout
770
+ if (toast.timeoutId) {
771
+ clearTimeout(toast.timeoutId);
772
+ toast.timeoutId = null;
773
+ }
774
+
775
toast.classList.remove('show');
776
toast.classList.add('hide');
777
739
- // Remove the element from display after animation completes
778
+ // Wait for the hide animation to complete before removing from display
779
setTimeout(() => {
780
toast.style.display = 'none';
781
toast.classList.remove('hide');
743
- }, 300); // Match this with animation duration
782
+ }, 400); // Match this with CSS transition duration
783
}
784
785
function scrollChanged(isAtBottom) {