Skip to content

Commit 69f7328

Browse files
authored
feat: add pinning functionality for inspector badge (#104)
1 parent 133f717 commit 69f7328

1 file changed

Lines changed: 117 additions & 8 deletions

File tree

src/view/frontend/web/js/inspector.js

Lines changed: 117 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ document.addEventListener('alpine:init', () => {
88
Alpine.data('mageforgeInspector', () => ({
99
isOpen: false,
1010
isPickerActive: false,
11+
isPinned: false, // Badge is locked after clicking an element
1112
hoveredElement: null,
1213
selectedElement: null,
1314
highlightBox: null,
@@ -248,7 +249,9 @@ document.addEventListener('alpine:init', () => {
248249
*/
249250
closeInspector() {
250251
this.isOpen = false;
252+
this.isPinned = false;
251253
this.deactivatePicker();
254+
this.hideHighlight();
252255
this.$dispatch('mageforge:inspector:closed');
253256
this.updateFloatingButton();
254257
},
@@ -276,9 +279,19 @@ document.addEventListener('alpine:init', () => {
276279
}
277280

278281
document.removeEventListener('mousemove', this.mouseMoveHandler);
279-
document.removeEventListener('click', this.clickHandler, false);
282+
283+
// Keep click handler active if pinned (for click-outside detection)
284+
if (!this.isPinned) {
285+
document.removeEventListener('click', this.clickHandler, false);
286+
}
287+
280288
document.body.style.cursor = '';
281-
this.hideHighlight();
289+
290+
// Only hide if not pinned
291+
if (!this.isPinned) {
292+
this.hideHighlight();
293+
}
294+
282295
this.hoveredElement = null;
283296
this.lastBadgeUpdate = 0;
284297
},
@@ -289,6 +302,9 @@ document.addEventListener('alpine:init', () => {
289302
handleMouseMove(e) {
290303
if (!this.isPickerActive) return;
291304

305+
// Don't update if badge is pinned
306+
if (this.isPinned) return;
307+
292308
// Don't update if mouse is over the floating button
293309
if (this.floatingButton && this.floatingButton.contains(e.target)) {
294310
return;
@@ -338,9 +354,20 @@ document.addEventListener('alpine:init', () => {
338354
* Handle click on element
339355
*/
340356
handleClick(e) {
357+
// Handle click outside badge when pinned
358+
if (this.isPinned && this.infoBadge) {
359+
// Check if click is outside badge
360+
if (!this.infoBadge.contains(e.target) && !this.floatingButton.contains(e.target)) {
361+
this.unpinBadge();
362+
return;
363+
}
364+
// Click inside badge - do nothing, let it stay open
365+
return;
366+
}
367+
341368
if (!this.isPickerActive) return;
342369

343-
// Don't handle clicks on the info badge
370+
// Don't handle clicks on the info badge during picking
344371
if (this.infoBadge && (this.infoBadge.contains(e.target) || this.infoBadge === e.target)) {
345372
return;
346373
}
@@ -353,11 +380,34 @@ document.addEventListener('alpine:init', () => {
353380
if (element) {
354381
this.selectedElement = element;
355382
this.updatePanelData(element);
356-
// Keep panel open but stop picking
357-
this.deactivatePicker();
383+
this.pinBadge();
384+
}
385+
},
386+
387+
/**
388+
* Pin the badge after element selection
389+
*/
390+
pinBadge() {
391+
this.isPinned = true;
392+
this.deactivatePicker();
393+
// Keep highlight and badge visible
394+
// Update badge to show close button
395+
if (this.selectedElement) {
396+
this.buildBadgeContent(this.selectedElement);
358397
}
359398
},
360399

400+
/**
401+
* Unpin and close the badge
402+
// Remove click handler now that we're unpinned
403+
document.removeEventListener('click', this.clickHandler, false);
404+
*/
405+
unpinBadge() {
406+
this.isPinned = false;
407+
this.hideHighlight();
408+
this.selectedElement = null;
409+
},
410+
361411
/**
362412
* Find nearest inspectable element
363413
*/
@@ -480,13 +530,73 @@ document.addEventListener('alpine:init', () => {
480530
// Clear badge
481531
this.infoBadge.innerHTML = '';
482532

533+
// Add close button if pinned
534+
if (this.isPinned) {
535+
this.infoBadge.appendChild(this.createCloseButton());
536+
}
537+
483538
// Create tab system
484539
this.createTabSystem(data, element);
485540

486541
// Branding footer
487542
this.infoBadge.appendChild(this.createBrandingFooter());
488543
},
489544

545+
/**
546+
* Create close button for pinned badge
547+
*/
548+
createCloseButton() {
549+
const closeBtn = document.createElement('button');
550+
closeBtn.type = 'button';
551+
closeBtn.className = 'mageforge-inspector-close';
552+
closeBtn.innerHTML = '✕';
553+
closeBtn.title = 'Close (or click outside)';
554+
closeBtn.style.cssText = `
555+
position: absolute;
556+
top: 12px;
557+
right: 12px;
558+
width: 28px;
559+
height: 28px;
560+
background: rgba(255, 255, 255, 0.05);
561+
border: 1px solid rgba(255, 255, 255, 0.1);
562+
border-radius: 6px;
563+
color: #94a3b8;
564+
font-size: 16px;
565+
font-weight: 600;
566+
cursor: pointer;
567+
display: flex;
568+
align-items: center;
569+
justify-content: center;
570+
transition: all 0.2s ease;
571+
z-index: 10;
572+
font-family: inherit;
573+
line-height: 1;
574+
padding: 0;
575+
`;
576+
577+
closeBtn.onmouseenter = () => {
578+
closeBtn.style.background = 'rgba(239, 68, 68, 0.15)';
579+
closeBtn.style.borderColor = 'rgba(239, 68, 68, 0.3)';
580+
closeBtn.style.color = '#ef4444';
581+
closeBtn.style.transform = 'scale(1.05)';
582+
};
583+
584+
closeBtn.onmouseleave = () => {
585+
closeBtn.style.background = 'rgba(255, 255, 255, 0.05)';
586+
closeBtn.style.borderColor = 'rgba(255, 255, 255, 0.1)';
587+
closeBtn.style.color = '#94a3b8';
588+
closeBtn.style.transform = 'scale(1)';
589+
};
590+
591+
closeBtn.onclick = (e) => {
592+
e.preventDefault();
593+
e.stopPropagation();
594+
this.unpinBadge();
595+
};
596+
597+
return closeBtn;
598+
},
599+
490600
/**
491601
* Create tab system for inspector
492602
*/
@@ -688,9 +798,8 @@ document.addEventListener('alpine:init', () => {
688798
* Render structure sections (template, block, module, etc.)
689799
*/
690800
renderStructureSections(data, container) {
691-
// Template section with override indicator
692-
const templateDisplay = data.isOverride ? '🔧 ' + data.template : data.template;
693-
container.appendChild(this.createInfoSection('📄 Template', templateDisplay, '#60a5fa'));
801+
// Template section
802+
container.appendChild(this.createInfoSection('📄 Template', data.template, '#60a5fa'));
694803

695804
// Block section
696805
container.appendChild(this.createInfoSection('📦 Block', data.blockClass, '#a78bfa'));

0 commit comments

Comments
 (0)