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;