From 6442e3cc6eb496e5884fff8e4995cd979444e3dd Mon Sep 17 00:00:00 2001 From: sunag Date: Sat, 6 Jun 2026 20:13:17 -0300 Subject: [PATCH] Inspector: Add unread warning and error notification badges (#33732) --- examples/jsm/inspector/tabs/Console.js | 126 +++++++++++++++++++++++++ examples/jsm/inspector/ui/Profiler.js | 10 ++ examples/jsm/inspector/ui/Style.js | 47 +++++++++ 3 files changed, 183 insertions(+) diff --git a/examples/jsm/inspector/tabs/Console.js b/examples/jsm/inspector/tabs/Console.js index c9e71eae8bc1a8..4462a82f3042b0 100644 --- a/examples/jsm/inspector/tabs/Console.js +++ b/examples/jsm/inspector/tabs/Console.js @@ -9,6 +9,24 @@ class Console extends Tab { this.filters = { info: true, warn: true, error: true }; this.filterText = ''; + this.unreadErrors = 0; + this.unreadWarns = 0; + + this.tabBadgeContainer = document.createElement( 'span' ); + this.tabBadgeContainer.className = 'tab-badge-container'; + + this.tabErrorBadge = document.createElement( 'span' ); + this.tabErrorBadge.className = 'tab-badge error'; + this.tabErrorBadge.style.display = 'none'; + + this.tabWarnBadge = document.createElement( 'span' ); + this.tabWarnBadge.className = 'tab-badge warn'; + this.tabWarnBadge.style.display = 'none'; + + this.tabBadgeContainer.appendChild( this.tabErrorBadge ); + this.tabBadgeContainer.appendChild( this.tabWarnBadge ); + this.button.appendChild( this.tabBadgeContainer ); + this.buildHeader(); this.logContainer = document.createElement( 'div' ); @@ -210,6 +228,95 @@ class Console extends Tab { } + setActive( isActive ) { + + super.setActive( isActive ); + + if ( isActive && this.profiler && this.profiler.panel.classList.contains( 'visible' ) ) { + + this.clearUnread(); + + } + + } + + clearUnread() { + + this.unreadErrors = 0; + this.unreadWarns = 0; + this.updateBadges(); + + } + + updateBadges() { + + if ( ! this.profiler ) return; + + const errorBadge = this.profiler.toggleButton.querySelector( '.console-badge.error' ); + const warnBadge = this.profiler.toggleButton.querySelector( '.console-badge.warn' ); + + if ( errorBadge ) { + + if ( this.unreadErrors > 0 ) { + + errorBadge.textContent = this.unreadErrors; + errorBadge.style.display = ''; + + } else { + + errorBadge.style.display = 'none'; + + } + + } + + if ( warnBadge ) { + + if ( this.unreadWarns > 0 ) { + + warnBadge.textContent = this.unreadWarns; + warnBadge.style.display = ''; + + } else { + + warnBadge.style.display = 'none'; + + } + + } + + if ( this.tabErrorBadge ) { + + if ( this.unreadErrors > 0 ) { + + this.tabErrorBadge.textContent = this.unreadErrors; + this.tabErrorBadge.style.display = ''; + + } else { + + this.tabErrorBadge.style.display = 'none'; + + } + + } + + if ( this.tabWarnBadge ) { + + if ( this.unreadWarns > 0 ) { + + this.tabWarnBadge.textContent = this.unreadWarns; + this.tabWarnBadge.style.display = ''; + + } else { + + this.tabWarnBadge.style.display = 'none'; + + } + + } + + } + addMessage( type, text ) { const msg = document.createElement( 'div' ); @@ -231,6 +338,25 @@ class Console extends Tab { } + // Update unread counts if the console is not active/visible + const isUnread = ! this.isActive; + + if ( isUnread ) { + + if ( type === 'error' ) { + + this.unreadErrors ++; + this.updateBadges(); + + } else if ( type === 'warn' ) { + + this.unreadWarns ++; + this.updateBadges(); + + } + + } + } } diff --git a/examples/jsm/inspector/ui/Profiler.js b/examples/jsm/inspector/ui/Profiler.js index 32d10caf9485a9..38a7eac2e53fec 100644 --- a/examples/jsm/inspector/ui/Profiler.js +++ b/examples/jsm/inspector/ui/Profiler.js @@ -250,6 +250,10 @@ export class Profiler extends EventDispatcher { + + + + `; this.toggleButton.onclick = () => this.togglePanel(); @@ -1626,6 +1630,12 @@ export class Profiler extends EventDispatcher { const isVisible = this.panel.classList.contains( 'visible' ); + if ( isVisible && this.activeTabId && this.tabs[ this.activeTabId ] ) { + + this.tabs[ this.activeTabId ].setActive( true ); + + } + this.detachedWindows.forEach( detachedWindow => { if ( isVisible ) { diff --git a/examples/jsm/inspector/ui/Style.js b/examples/jsm/inspector/ui/Style.js index aa8c96071da319..3c0aca85227564 100644 --- a/examples/jsm/inspector/ui/Style.js +++ b/examples/jsm/inspector/ui/Style.js @@ -74,6 +74,7 @@ export class Style { } .toggle-icon { + position: relative; display: flex; align-items: center; justify-content: center; @@ -82,6 +83,52 @@ export class Style { transition: background-color 0.2s; } + .console-badge-container { + position: absolute; + top: 2px; + right: 2px; + display: flex; + gap: 2px; + pointer-events: none; + } + + .console-badge, + .tab-badge { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 14px; + height: 14px; + padding: 0 4px; + border-radius: 7px; + font-size: 9px; + font-weight: bold; + color: #ffffff; + line-height: 1; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4); + border: 1px solid rgba(0, 0, 0, 0.2); + } + + .tab-badge-container { + position: absolute; + top: 2px; + right: 3px; + display: flex; + gap: 2px; + pointer-events: none; + } + + .console-badge.error, + .tab-badge.error { + background-color: var(--color-red); + } + + .console-badge.warn, + .tab-badge.warn { + background-color: var(--color-yellow); + color: #111111; + } + .profiler-toggle:hover .toggle-icon { background-color: rgba(255, 255, 255, 0.05); }