diff --git a/examples/jsm/inspector/Inspector.js b/examples/jsm/inspector/Inspector.js index ae09f9a0f96b09..49b32a7002bcb2 100644 --- a/examples/jsm/inspector/Inspector.js +++ b/examples/jsm/inspector/Inspector.js @@ -466,7 +466,7 @@ class Inspector extends RendererInspector { if ( this.displayCycle.text.needsUpdate ) { - setText( this.profiler.toggleButton.querySelector('.fps-counter'), this.fps.toFixed() ); + setText( this.profiler.toggleButton.querySelector( '.fps-counter' ), this.fps.toFixed() ); this.performance.updateText( this, frame ); this.memory.updateText( this ); @@ -498,25 +498,21 @@ class Inspector extends RendererInspector { } - static getItem( id ) { +} - console.warn( 'Inspector.getItem is deprecated. Use getItem directly instead.' ); - return getItem( id ); +function getItem( id ) { - } + const data = JSON.parse( localStorage.getItem( 'threejs-inspector' ) || '{}' ); - static setItem( id, state ) { + if ( data.version !== REVISION || + data.settings && ( data.settings.storage === 'url' && data.settings.url !== location.href ) ) { - console.warn( 'Inspector.setItem is deprecated. Use setItem directly instead.' ); - setItem( id, state ); + localStorage.removeItem( 'threejs-inspector' ); - } + return {}; -} - -function getItem( id ) { + } - const data = JSON.parse( localStorage.getItem( 'threejs-inspector' ) || '{}' ); return data[ id ] || {}; } @@ -535,6 +531,11 @@ function setItem( id, state ) { } + data.settings = data.settings || {}; + data.settings.url = data.settings.url || location.href; + data.settings.storage = data.settings.storage || 'url'; + data.version = REVISION; + localStorage.setItem( 'threejs-inspector', JSON.stringify( data ) ); } diff --git a/examples/jsm/inspector/tabs/Parameters.js b/examples/jsm/inspector/tabs/Parameters.js index f0e9d34a716194..d3c54a4789d8b9 100644 --- a/examples/jsm/inspector/tabs/Parameters.js +++ b/examples/jsm/inspector/tabs/Parameters.js @@ -66,16 +66,90 @@ class ParametersGroup { } + _addInfo( editor, itemNode ) { + + editor.info = ( text ) => { + + let infoIcon = itemNode.querySelector( '.info-icon' ); + + if ( ! infoIcon ) { + + infoIcon = document.createElement( 'span' ); + infoIcon.className = 'info-icon'; + infoIcon.textContent = 'i'; + + itemNode.appendChild( infoIcon ); + + infoIcon.addEventListener( 'mouseenter', () => { + + const container = infoIcon.closest( '.three-inspector' ) || document.body; + let tooltip = container.querySelector( '.three-inspector-info-tooltip' ); + + if ( ! tooltip ) { + + tooltip = document.createElement( 'div' ); + tooltip.className = 'info-tooltip three-inspector-info-tooltip'; + container.appendChild( tooltip ); + + } + + const html = text.trim().replace( /### (.*?)(?:\r?\n|$)/g, '

$1

' ) + .replace( /\*\*(.*?)\*\*/g, '$1' ) + .replace( /\n/g, '
' ); + + tooltip.innerHTML = html; + + const rect = infoIcon.getBoundingClientRect(); + + tooltip.style.left = ( rect.left + rect.width / 2 ) + 'px'; + tooltip.style.top = ( rect.top - 8 ) + 'px'; + + tooltip.style.opacity = '1'; + tooltip.style.visibility = 'visible'; + + } ); + + infoIcon.addEventListener( 'mouseleave', () => { + + const container = infoIcon.closest( '.three-inspector' ) || document.body; + const tooltip = container.querySelector( '.three-inspector-info-tooltip' ); + if ( tooltip ) { + + tooltip.style.opacity = '0'; + tooltip.style.visibility = 'hidden'; + + } + + } ); + + } + + return editor; + + }; + + } + _addParameter( object, property, editor, subItem ) { editor.name = ( name ) => { - subItem.data[ 0 ].textContent = name; + if ( subItem.data[ 0 ].childNodes.length > 0 && subItem.data[ 0 ].firstChild.nodeType === 3 /* Node.TEXT_NODE */ ) { + + subItem.data[ 0 ].firstChild.textContent = name; + + } else { + + subItem.data[ 0 ].insertBefore( document.createTextNode( name ), subItem.data[ 0 ].firstChild ); + + } return editor; }; + this._addInfo( editor, subItem.data[ 0 ] ); + editor.listen = () => { const update = () => { @@ -328,12 +402,24 @@ class ParametersGroup { editor.name = ( name ) => { - editor.domElement.childNodes[ 0 ].textContent = name; + const buttonNode = editor.domElement.childNodes[ 0 ]; + + if ( buttonNode.childNodes.length > 0 && buttonNode.firstChild.nodeType === 3 /* Node.TEXT_NODE */ ) { + + buttonNode.firstChild.textContent = name; + + } else { + + buttonNode.insertBefore( document.createTextNode( name ), buttonNode.firstChild ); + + } return editor; }; + this._addInfo( editor, editor.domElement.childNodes[ 0 ] ); + this._registerParameter( object, property, editor, subItem ); return editor; diff --git a/examples/jsm/inspector/tabs/Settings.js b/examples/jsm/inspector/tabs/Settings.js index c7e4c3794bdc73..579bce46d946df 100644 --- a/examples/jsm/inspector/tabs/Settings.js +++ b/examples/jsm/inspector/tabs/Settings.js @@ -43,7 +43,8 @@ function _loadState() { _state = { forceWebGL: settings.forceWebGL !== undefined ? settings.forceWebGL : false, captureStackTrace: settings.captureStackTrace !== undefined ? settings.captureStackTrace : false, - activeExtensions: settings.activeExtensions !== undefined ? settings.activeExtensions : {} + activeExtensions: settings.activeExtensions !== undefined ? settings.activeExtensions : {}, + storage: settings.storage !== undefined ? settings.storage : 'url' }; if ( _state.forceWebGL ) { @@ -67,7 +68,8 @@ function _saveState() { setItem( 'settings', { forceWebGL: _state.forceWebGL, captureStackTrace: _state.captureStackTrace, - activeExtensions: _state.activeExtensions + activeExtensions: _state.activeExtensions, + storage: _state.storage } ); } @@ -114,6 +116,35 @@ class Settings extends Parameters { const extensionsGroup = this.createGroup( 'Extensions' ); + const storageGroup = this.createGroup( 'Storage' ); + + const currentState = _loadState(); + + storageGroup.add( currentState, 'storage', { 'URL Session': 'url', 'Keep across Origin': 'origin' } ) + .name( 'Save Settings' ) + .onChange( () => { + + _saveState(); + + } ).info( ` +Defines how the **Inspector** preferences and states are stored in the browser. + +**URL Session** +Saves state based on the exact URL. It will reset the settings whenever the URL changes. + +**Keep across Origin** +Shares the same state across any page within the current origin.` ); + + storageGroup.add( { + clear: () => { + + localStorage.removeItem( 'threejs-inspector' ); + + location.reload(); + + } + }, 'clear' ).name( 'Clear Settings' ); + this._getExtensions().then( extensions => { for ( const extension of extensions ) { diff --git a/examples/jsm/inspector/ui/Style.js b/examples/jsm/inspector/ui/Style.js index fdf6e530576c94..aa8c96071da319 100644 --- a/examples/jsm/inspector/ui/Style.js +++ b/examples/jsm/inspector/ui/Style.js @@ -288,6 +288,61 @@ export class Style { cursor: pointer; } + .info-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 14px; + height: 14px; + border-radius: 50%; + background-color: rgba(255, 255, 255, 0.1); + color: var(--text-secondary); + font-size: 10px; + font-style: italic; + margin-left: 6px; + cursor: help; + position: relative; + } + + .info-icon:hover { + background-color: var(--color-accent); + color: white; + } + + .info-tooltip { + position: fixed; + transform: translate(-50%, -100%); + background-color: rgba(30, 30, 36, 0.95); + border: 1px solid var(--profiler-border); + border-radius: 6px; + padding: 10px 14px; + color: var(--text-primary); + font-size: 12px; + width: max-content; + max-width: 250px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); + pointer-events: none; + opacity: 0; + visibility: hidden; + transition: opacity 0.2s, visibility 0.2s; + z-index: 999999; + font-style: normal; + font-family: var(--font-family); + text-align: left; + white-space: normal; + } + + .info-tooltip h3 { + margin: 0 0 6px 0; + font-size: 13px; + color: var(--color-accent); + } + + .info-tooltip strong { + font-weight: 600; + color: white; + } + /* Style adjustments for lil-gui look */ .mini-panel-content .item-row { padding: 3px 8px;