feat(webkit): add zoom level toast notifications with debouncing
- Add __dumber_showZoomToast function for displaying zoom changes - Implement proper debouncing to prevent toast spam during rapid zoom - Add aggressive cleanup to prevent stuck/colliding toasts - Include emergency cleanup function for orphaned toasts - Toast shows format: 'Zoom level: ±X% (Y%)' - 150ms debounce with 2s display duration
- commit
- ea758f494489cadba618f67d58d73ef4e008fbbf
- author
- bamen · · 2025-09-11 13:45 UTC
- parent
- 51432f4
- signature
- unsigned
1 file · +63 -0
M pkg/webkit/toast_js_cgo.go +63 -0
| @@ -14,6 +14,8 @@ func getToastScript() string { | |||
| 14 | 14 | container: null, | |
| 15 | 15 | toasts: [], // {id, element, timeout} | |
| 16 | 16 | counter: 0, | |
| 17 | + | zoomToastId: null, | |
| 18 | + | zoomDebounceTimer: null, | |
| 17 | 19 | ||
| 18 | 20 | init() { | |
| 19 | 21 | if (T.container) return; | |
| @@ -99,6 +101,11 @@ func getToastScript() string { | |||
| 99 | 101 | const toast = T.toasts[toastIndex]; | |
| 100 | 102 | clearTimeout(toast.timeout); | |
| 101 | 103 | ||
| 104 | + | // Clear zoom toast tracking if this is the zoom toast | |
| 105 | + | if (T.zoomToastId === id) { | |
| 106 | + | T.zoomToastId = null; | |
| 107 | + | } | |
| 108 | + | ||
| 102 | 109 | // Animate out | |
| 103 | 110 | toast.element.style.transform = 'translateX(100%)'; | |
| 104 | 111 | toast.element.style.opacity = '0'; | |
| @@ -119,6 +126,9 @@ func getToastScript() string { | |||
| 119 | 126 | } | |
| 120 | 127 | }); | |
| 121 | 128 | T.toasts = []; | |
| 129 | + | T.zoomToastId = null; | |
| 130 | + | clearTimeout(T.zoomDebounceTimer); | |
| 131 | + | T.zoomDebounceTimer = null; | |
| 122 | 132 | } | |
| 123 | 133 | }; | |
| 124 | 134 | ||
| @@ -126,6 +136,59 @@ func getToastScript() string { | |||
| 126 | 136 | window.__dumber_showToast = (message, duration) => T.show(message, duration); | |
| 127 | 137 | window.__dumber_dismissToast = (id) => T.dismiss(id); | |
| 128 | 138 | window.__dumber_clearToasts = () => T.clear(); | |
| 139 | + | window.__dumber_showZoomToast = (zoomLevel) => { | |
| 140 | + | console.log('[dumber] showZoomToast called with level:', zoomLevel); | |
| 141 | + | ||
| 142 | + | // Clear existing debounce timer | |
| 143 | + | clearTimeout(T.zoomDebounceTimer); | |
| 144 | + | ||
| 145 | + | // Force remove ALL zoom toasts immediately (aggressive cleanup) | |
| 146 | + | for (let i = T.toasts.length - 1; i >= 0; i--) { | |
| 147 | + | const toast = T.toasts[i]; | |
| 148 | + | if (toast.element && (toast.element.textContent.includes('Zoom level:') || toast.id === T.zoomToastId)) { | |
| 149 | + | clearTimeout(toast.timeout); | |
| 150 | + | if (toast.element.parentNode) { | |
| 151 | + | toast.element.parentNode.removeChild(toast.element); | |
| 152 | + | } | |
| 153 | + | T.toasts.splice(i, 1); | |
| 154 | + | } | |
| 155 | + | } | |
| 156 | + | T.zoomToastId = null; | |
| 157 | + | ||
| 158 | + | // Also cleanup any orphaned zoom toast elements in DOM | |
| 159 | + | const orphanedZoomToasts = document.querySelectorAll('.dumber-toast'); | |
| 160 | + | orphanedZoomToasts.forEach(el => { | |
| 161 | + | if (el.textContent.includes('Zoom level:')) { | |
| 162 | + | el.parentNode && el.parentNode.removeChild(el); | |
| 163 | + | } | |
| 164 | + | }); | |
| 165 | + | ||
| 166 | + | // Debounce the toast display | |
| 167 | + | T.zoomDebounceTimer = setTimeout(() => { | |
| 168 | + | const percentage = Math.round(zoomLevel * 100); | |
| 169 | + | const sign = percentage > 100 ? '+' : ''; | |
| 170 | + | const diff = percentage - 100; | |
| 171 | + | const message = 'Zoom level: ' + sign + diff + '% (' + percentage + '%)'; | |
| 172 | + | console.log('[dumber] Showing zoom toast:', message); | |
| 173 | + | T.zoomToastId = T.show(message, 2000); | |
| 174 | + | }, 150); | |
| 175 | + | }; | |
| 176 | + | ||
| 177 | + | // Test function for manual debugging | |
| 178 | + | window.__dumber_testToast = () => { | |
| 179 | + | console.log('[dumber] Testing toast system...'); | |
| 180 | + | return T.show('Toast system is working!', 2000); | |
| 181 | + | }; | |
| 182 | + | ||
| 183 | + | // Emergency cleanup function for stuck toasts | |
| 184 | + | window.__dumber_forceCleanupToasts = () => { | |
| 185 | + | console.log('[dumber] Force cleaning up all toasts...'); | |
| 186 | + | T.clear(); | |
| 187 | + | // Also force remove any orphaned toast elements | |
| 188 | + | const orphans = document.querySelectorAll('.dumber-toast'); | |
| 189 | + | orphans.forEach(el => el.parentNode && el.parentNode.removeChild(el)); | |
| 190 | + | console.log('[dumber] Cleanup complete, removed', orphans.length, 'orphaned toasts'); | |
| 191 | + | }; | |
| 129 | 192 | ||
| 130 | 193 | console.log('✅ Dumber Browser toast system loaded'); | |
| 131 | 194 | ||
| @@ -14,6 +14,8 @@ func getToastScript() string { | |||||
| 14 | container: null, | 14 | container: null, | ||
| 15 | toasts: [], // {id, element, timeout} | 15 | toasts: [], // {id, element, timeout} | ||
| 16 | counter: 0, | 16 | counter: 0, | ||
| 17 | + | zoomToastId: null, | |||
| 18 | + | zoomDebounceTimer: null, | |||
| 17 | 19 | ||||
| 18 | init() { | 20 | init() { | ||
| 19 | if (T.container) return; | 21 | if (T.container) return; | ||
| @@ -99,6 +101,11 @@ func getToastScript() string { | |||||
| 99 | const toast = T.toasts[toastIndex]; | 101 | const toast = T.toasts[toastIndex]; | ||
| 100 | clearTimeout(toast.timeout); | 102 | clearTimeout(toast.timeout); | ||
| 101 | 103 | ||||
| 104 | + | // Clear zoom toast tracking if this is the zoom toast | |||
| 105 | + | if (T.zoomToastId === id) { | |||
| 106 | + | T.zoomToastId = null; | |||
| 107 | + | } | |||
| 108 | + | ||||
| 102 | // Animate out | 109 | // Animate out | ||
| 103 | toast.element.style.transform = 'translateX(100%)'; | 110 | toast.element.style.transform = 'translateX(100%)'; | ||
| 104 | toast.element.style.opacity = '0'; | 111 | toast.element.style.opacity = '0'; | ||
| @@ -119,6 +126,9 @@ func getToastScript() string { | |||||
| 119 | } | 126 | } | ||
| 120 | }); | 127 | }); | ||
| 121 | T.toasts = []; | 128 | T.toasts = []; | ||
| 129 | + | T.zoomToastId = null; | |||
| 130 | + | clearTimeout(T.zoomDebounceTimer); | |||
| 131 | + | T.zoomDebounceTimer = null; | |||
| 122 | } | 132 | } | ||
| 123 | }; | 133 | }; | ||
| 124 | 134 | ||||
| @@ -126,6 +136,59 @@ func getToastScript() string { | |||||
| 126 | window.__dumber_showToast = (message, duration) => T.show(message, duration); | 136 | window.__dumber_showToast = (message, duration) => T.show(message, duration); | ||
| 127 | window.__dumber_dismissToast = (id) => T.dismiss(id); | 137 | window.__dumber_dismissToast = (id) => T.dismiss(id); | ||
| 128 | window.__dumber_clearToasts = () => T.clear(); | 138 | window.__dumber_clearToasts = () => T.clear(); | ||
| 139 | + | window.__dumber_showZoomToast = (zoomLevel) => { | |||
| 140 | + | console.log('[dumber] showZoomToast called with level:', zoomLevel); | |||
| 141 | + | ||||
| 142 | + | // Clear existing debounce timer | |||
| 143 | + | clearTimeout(T.zoomDebounceTimer); | |||
| 144 | + | ||||
| 145 | + | // Force remove ALL zoom toasts immediately (aggressive cleanup) | |||
| 146 | + | for (let i = T.toasts.length - 1; i >= 0; i--) { | |||
| 147 | + | const toast = T.toasts[i]; | |||
| 148 | + | if (toast.element && (toast.element.textContent.includes('Zoom level:') || toast.id === T.zoomToastId)) { | |||
| 149 | + | clearTimeout(toast.timeout); | |||
| 150 | + | if (toast.element.parentNode) { | |||
| 151 | + | toast.element.parentNode.removeChild(toast.element); | |||
| 152 | + | } | |||
| 153 | + | T.toasts.splice(i, 1); | |||
| 154 | + | } | |||
| 155 | + | } | |||
| 156 | + | T.zoomToastId = null; | |||
| 157 | + | ||||
| 158 | + | // Also cleanup any orphaned zoom toast elements in DOM | |||
| 159 | + | const orphanedZoomToasts = document.querySelectorAll('.dumber-toast'); | |||
| 160 | + | orphanedZoomToasts.forEach(el => { | |||
| 161 | + | if (el.textContent.includes('Zoom level:')) { | |||
| 162 | + | el.parentNode && el.parentNode.removeChild(el); | |||
| 163 | + | } | |||
| 164 | + | }); | |||
| 165 | + | ||||
| 166 | + | // Debounce the toast display | |||
| 167 | + | T.zoomDebounceTimer = setTimeout(() => { | |||
| 168 | + | const percentage = Math.round(zoomLevel * 100); | |||
| 169 | + | const sign = percentage > 100 ? '+' : ''; | |||
| 170 | + | const diff = percentage - 100; | |||
| 171 | + | const message = 'Zoom level: ' + sign + diff + '% (' + percentage + '%)'; | |||
| 172 | + | console.log('[dumber] Showing zoom toast:', message); | |||
| 173 | + | T.zoomToastId = T.show(message, 2000); | |||
| 174 | + | }, 150); | |||
| 175 | + | }; | |||
| 176 | + | ||||
| 177 | + | // Test function for manual debugging | |||
| 178 | + | window.__dumber_testToast = () => { | |||
| 179 | + | console.log('[dumber] Testing toast system...'); | |||
| 180 | + | return T.show('Toast system is working!', 2000); | |||
| 181 | + | }; | |||
| 182 | + | ||||
| 183 | + | // Emergency cleanup function for stuck toasts | |||
| 184 | + | window.__dumber_forceCleanupToasts = () => { | |||
| 185 | + | console.log('[dumber] Force cleaning up all toasts...'); | |||
| 186 | + | T.clear(); | |||
| 187 | + | // Also force remove any orphaned toast elements | |||
| 188 | + | const orphans = document.querySelectorAll('.dumber-toast'); | |||
| 189 | + | orphans.forEach(el => el.parentNode && el.parentNode.removeChild(el)); | |||
| 190 | + | console.log('[dumber] Cleanup complete, removed', orphans.length, 'orphaned toasts'); | |||
| 191 | + | }; | |||
| 129 | 192 | ||||
| 130 | console.log('✅ Dumber Browser toast system loaded'); | 193 | console.log('✅ Dumber Browser toast system loaded'); | ||
| 131 | 194 | ||||