Skip to content

Commit cbd5251

Browse files
committed
Devtools: Request Object Details
1 parent d86b81b commit cbd5251

File tree

5 files changed

+243
-2
lines changed

5 files changed

+243
-2
lines changed

devtools/background.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ chrome.runtime.onConnect.addListener( port => {
2020

2121
chrome.tabs.sendMessage( tabId, message );
2222

23+
} else if ( message.name === 'request-object-details' && tabId ) {
24+
25+
chrome.tabs.sendMessage( tabId, message );
26+
2327
} else if ( tabId === undefined ) {
2428

2529
console.warn( 'Background: Message received from panel before init:', message );

devtools/bridge.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,10 @@
372372

373373
sendState();
374374

375+
} else if ( message.name === 'request-object-details' ) {
376+
377+
sendObjectDetails( message.uuid );
378+
375379
}
376380

377381
} );
@@ -400,6 +404,57 @@
400404

401405
}
402406

407+
function sendObjectDetails( uuid ) {
408+
409+
// Find the object with the given UUID
410+
const findObjectInScenes = ( targetUuid ) => {
411+
412+
for ( const scene of observedScenes ) {
413+
414+
const found = scene.getObjectByProperty( 'uuid', targetUuid );
415+
if ( found ) return found;
416+
417+
}
418+
419+
return null;
420+
421+
};
422+
423+
const object = findObjectInScenes( uuid );
424+
425+
if ( object ) {
426+
427+
const details = {
428+
uuid: object.uuid,
429+
type: object.type,
430+
name: object.name,
431+
position: {
432+
x: object.position.x,
433+
y: object.position.y,
434+
z: object.position.z
435+
},
436+
rotation: {
437+
x: object.rotation.x,
438+
y: object.rotation.y,
439+
z: object.rotation.z
440+
},
441+
scale: {
442+
x: object.scale.x,
443+
y: object.scale.y,
444+
z: object.scale.z
445+
}
446+
};
447+
448+
dispatchEvent( 'object-details', details );
449+
450+
} else {
451+
452+
console.warn( 'DevTools: Object not found for UUID:', uuid );
453+
454+
}
455+
456+
}
457+
403458
function dispatchEvent( name, detail ) {
404459

405460
try {

devtools/content-script.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,11 @@ function handleBackgroundMessage( message ) {
109109
message.id = 'three-devtools';
110110
window.postMessage( message, '*' );
111111

112+
} else if ( message.name === 'request-object-details' ) {
113+
114+
message.id = 'three-devtools';
115+
window.postMessage( message, '*' );
116+
112117
}
113118

114119
}

devtools/panel/panel.css

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,4 +112,41 @@ details.renderer-container > summary.renderer-summary::-webkit-details-marker {
112112
}
113113
details.renderer-container[open] > summary.renderer-summary .toggle-icon::before {
114114
content: '▼'; /* Expanded */
115+
}
116+
117+
/* Floating object details panel */
118+
.floating-details {
119+
position: fixed;
120+
z-index: 1000;
121+
background: light-dark( #fff, #2a2a2a );
122+
border: 1px solid light-dark( #ccc, #555 );
123+
border-radius: 6px;
124+
padding: 12px;
125+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
126+
max-width: 300px;
127+
min-width: 200px;
128+
font-size: 11px;
129+
pointer-events: none; /* Prevent interfering with mouse interactions */
130+
opacity: 0;
131+
transform: translateY(10px);
132+
transition: opacity 0.2s ease, transform 0.2s ease;
133+
}
134+
135+
.floating-details.visible {
136+
opacity: 1;
137+
transform: translateY(0);
138+
}
139+
140+
.floating-details h4 {
141+
margin: 8px 0 4px 0;
142+
font-size: 10px;
143+
text-transform: uppercase;
144+
color: light-dark( #666, #aaa );
145+
border-bottom: 1px solid light-dark( #eee, #444 );
146+
padding-bottom: 2px;
147+
}
148+
149+
.floating-details .property-row {
150+
margin-bottom: 1px;
151+
font-size: 10px;
115152
}

devtools/panel/panel.js

Lines changed: 142 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ function createPropertyRow(label, value) {
2020

2121
const labelSpan = document.createElement('span');
2222
labelSpan.className = 'property-label';
23-
labelSpan.textContent = `${label}:`;
23+
labelSpan.textContent = `${label}`;
2424
labelSpan.style.marginRight = '10px';
2525
labelSpan.style.whiteSpace = 'nowrap';
2626

@@ -37,14 +37,34 @@ function createPropertyRow(label, value) {
3737
return row;
3838
}
3939

40+
function createVectorRow(label, vector) {
41+
const row = document.createElement('div');
42+
row.className = 'property-row';
43+
row.style.marginBottom = '2px';
44+
45+
// Pad label to ensure consistent alignment
46+
const paddedLabel = label.padEnd(16, ' '); // Pad to 16 characters
47+
const content = `${paddedLabel} ${vector.x.toFixed(3)}\t${vector.y.toFixed(3)}\t${vector.z.toFixed(3)}`;
48+
row.textContent = content;
49+
row.style.fontFamily = 'monospace';
50+
row.style.whiteSpace = 'pre';
51+
52+
return row;
53+
}
54+
4055
// --- State ---
4156
const state = {
4257
revision: null,
4358
scenes: new Map(),
4459
renderers: new Map(),
45-
objects: new Map()
60+
objects: new Map(),
61+
selectedObject: null
4662
};
4763

64+
// Floating details panel
65+
let floatingPanel = null;
66+
let mousePosition = { x: 0, y: 0 };
67+
4868

4969
// Create a connection to the background page
5070
const backgroundPageConnection = chrome.runtime.connect( {
@@ -80,6 +100,15 @@ backgroundPageConnection.onDisconnect.addListener( () => {
80100

81101
} );
82102

103+
// Function to request object details from the bridge
104+
function requestObjectDetails( uuid ) {
105+
backgroundPageConnection.postMessage( {
106+
name: 'request-object-details',
107+
uuid: uuid,
108+
tabId: chrome.devtools.inspectedWindow.tabId
109+
} );
110+
}
111+
83112

84113
// Store renderer collapse states
85114
const rendererCollapsedState = new Map();
@@ -98,6 +127,13 @@ function clearState() {
98127

99128
}
100129

130+
// Hide floating panel
131+
if ( floatingPanel ) {
132+
133+
floatingPanel.classList.remove( 'visible' );
134+
135+
}
136+
101137
}
102138

103139
// Listen for messages from the background page
@@ -139,6 +175,13 @@ function handleThreeEvent( message ) {
139175

140176
break;
141177

178+
// Handle object details response
179+
case 'object-details':
180+
state.selectedObject = message.detail;
181+
console.log( 'Panel: Received object details:', message.detail );
182+
showFloatingDetails( message.detail );
183+
break;
184+
142185
// Handle a batch of objects for a specific scene
143186
case 'scene':
144187
const { sceneUuid, objects: batchObjects } = message.detail;
@@ -376,6 +419,13 @@ function renderObject( obj, container, level = 0 ) {
376419
}
377420

378421
elem.innerHTML = labelContent;
422+
423+
// Add mouseover handler to request object details
424+
elem.addEventListener( 'mouseover', ( event ) => {
425+
event.stopPropagation(); // Prevent bubbling to parent elements
426+
requestObjectDetails( obj.uuid );
427+
} );
428+
379429
container.appendChild( elem );
380430

381431
// Handle children (excluding children of renderers, as properties are shown in details)
@@ -477,8 +527,98 @@ function updateUI() {
477527

478528
}
479529

530+
480531
}
481532

533+
// Create floating details panel
534+
function createFloatingPanel() {
535+
536+
if ( floatingPanel ) return floatingPanel;
537+
538+
floatingPanel = document.createElement( 'div' );
539+
floatingPanel.className = 'floating-details';
540+
document.body.appendChild( floatingPanel );
541+
542+
return floatingPanel;
543+
544+
}
545+
546+
// Show floating details panel
547+
function showFloatingDetails( objectData ) {
548+
549+
const panel = createFloatingPanel();
550+
551+
// Clear previous content
552+
panel.innerHTML = '';
553+
554+
if ( objectData.position ) {
555+
556+
panel.appendChild( createVectorRow( 'Position', objectData.position ) );
557+
558+
}
559+
560+
if ( objectData.rotation ) {
561+
562+
panel.appendChild( createVectorRow( 'Rotation', objectData.rotation ) );
563+
564+
}
565+
566+
if ( objectData.scale ) {
567+
568+
panel.appendChild( createVectorRow( 'Scale', objectData.scale ) );
569+
570+
}
571+
572+
// Position panel near mouse
573+
updateFloatingPanelPosition();
574+
575+
// Show panel
576+
panel.classList.add( 'visible' );
577+
578+
}
579+
580+
// Update floating panel position
581+
function updateFloatingPanelPosition() {
582+
583+
if ( ! floatingPanel || ! floatingPanel.classList.contains( 'visible' ) ) return;
584+
585+
const offset = 15; // Offset from cursor
586+
let x = mousePosition.x + offset;
587+
let y = mousePosition.y + offset;
588+
589+
// Prevent panel from going off-screen
590+
const panelRect = floatingPanel.getBoundingClientRect();
591+
const maxX = window.innerWidth - panelRect.width - 10;
592+
const maxY = window.innerHeight - panelRect.height - 10;
593+
594+
if ( x > maxX ) x = mousePosition.x - panelRect.width - offset;
595+
if ( y > maxY ) y = mousePosition.y - panelRect.height - offset;
596+
597+
floatingPanel.style.left = `${Math.max( 10, x )}px`;
598+
floatingPanel.style.top = `${Math.max( 10, y )}px`;
599+
600+
}
601+
602+
// Track mouse position
603+
document.addEventListener( 'mousemove', ( event ) => {
604+
605+
mousePosition.x = event.clientX;
606+
mousePosition.y = event.clientY;
607+
updateFloatingPanelPosition();
608+
609+
} );
610+
611+
// Hide panel when mouse leaves the tree area
612+
document.addEventListener( 'mouseover', ( event ) => {
613+
614+
if ( floatingPanel && ! event.target.closest( '.tree-item' ) ) {
615+
616+
floatingPanel.classList.remove( 'visible' );
617+
618+
}
619+
620+
} );
621+
482622
// Initial UI update
483623
clearState();
484624
updateUI();

0 commit comments

Comments
 (0)