mirror of
https://github.com/stackblitz-labs/bolt.diy
synced 2025-06-26 18:26:38 +00:00
- Implement element inspector tool for preview iframe with hover/click detection - Add inspector panel UI to display element details and styles - Integrate selected elements into chat messages for reference - Style improvements for chat messages and scroll behavior - Add inspector script injection to preview iframe - Support element selection and context in chat prompts -Redesign Messgaes, Workbench and Header for a more refined look allowing more workspace in view
292 lines
8.5 KiB
JavaScript
292 lines
8.5 KiB
JavaScript
(function() {
|
|
let isInspectorActive = false;
|
|
let inspectorStyle = null;
|
|
let currentHighlight = null;
|
|
|
|
// Function to get relevant styles
|
|
function getRelevantStyles(element) {
|
|
const computedStyles = window.getComputedStyle(element);
|
|
const relevantProps = [
|
|
'display', 'position', 'width', 'height', 'margin', 'padding',
|
|
'border', 'background', 'color', 'font-size', 'font-family',
|
|
'text-align', 'flex-direction', 'justify-content', 'align-items'
|
|
];
|
|
|
|
const styles = {};
|
|
relevantProps.forEach(prop => {
|
|
const value = computedStyles.getPropertyValue(prop);
|
|
if (value) styles[prop] = value;
|
|
});
|
|
|
|
return styles;
|
|
}
|
|
|
|
// Function to create a readable element selector
|
|
function createReadableSelector(element) {
|
|
let selector = element.tagName.toLowerCase();
|
|
|
|
// Add ID if present
|
|
if (element.id) {
|
|
selector += `#${element.id}`;
|
|
}
|
|
|
|
// Add classes if present
|
|
let className = '';
|
|
if (element.className) {
|
|
if (typeof element.className === 'string') {
|
|
className = element.className;
|
|
} else if (element.className.baseVal !== undefined) {
|
|
className = element.className.baseVal;
|
|
} else {
|
|
className = element.className.toString();
|
|
}
|
|
|
|
if (className.trim()) {
|
|
const classes = className.trim().split(/\s+/).slice(0, 3); // Limit to first 3 classes
|
|
selector += `.${classes.join('.')}`;
|
|
}
|
|
}
|
|
|
|
return selector;
|
|
}
|
|
|
|
// Function to create element display text
|
|
function createElementDisplayText(element) {
|
|
const tagName = element.tagName.toLowerCase();
|
|
let displayText = `<${tagName}`;
|
|
|
|
// Add ID attribute
|
|
if (element.id) {
|
|
displayText += ` id="${element.id}"`;
|
|
}
|
|
|
|
// Add class attribute (limit to first 3 classes for readability)
|
|
let className = '';
|
|
if (element.className) {
|
|
if (typeof element.className === 'string') {
|
|
className = element.className;
|
|
} else if (element.className.baseVal !== undefined) {
|
|
className = element.className.baseVal;
|
|
} else {
|
|
className = element.className.toString();
|
|
}
|
|
|
|
if (className.trim()) {
|
|
const classes = className.trim().split(/\s+/);
|
|
const displayClasses = classes.length > 3 ?
|
|
classes.slice(0, 3).join(' ') + '...' :
|
|
classes.join(' ');
|
|
displayText += ` class="${displayClasses}"`;
|
|
}
|
|
}
|
|
|
|
// Add other important attributes
|
|
const importantAttrs = ['type', 'name', 'href', 'src', 'alt', 'title'];
|
|
importantAttrs.forEach(attr => {
|
|
const value = element.getAttribute(attr);
|
|
if (value) {
|
|
const truncatedValue = value.length > 30 ? value.substring(0, 30) + '...' : value;
|
|
displayText += ` ${attr}="${truncatedValue}"`;
|
|
}
|
|
});
|
|
|
|
displayText += '>';
|
|
|
|
// Add text content preview for certain elements
|
|
const textElements = ['span', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'button', 'a', 'label'];
|
|
if (textElements.includes(tagName) && element.textContent) {
|
|
const textPreview = element.textContent.trim().substring(0, 50);
|
|
if (textPreview) {
|
|
displayText += textPreview.length < element.textContent.trim().length ?
|
|
textPreview + '...' : textPreview;
|
|
}
|
|
}
|
|
|
|
displayText += `</${tagName}>`;
|
|
|
|
return displayText;
|
|
}
|
|
|
|
// Function to create element info
|
|
function createElementInfo(element) {
|
|
const rect = element.getBoundingClientRect();
|
|
|
|
return {
|
|
tagName: element.tagName,
|
|
className: getElementClassName(element),
|
|
id: element.id || '',
|
|
textContent: element.textContent?.slice(0, 100) || '',
|
|
styles: getRelevantStyles(element),
|
|
rect: {
|
|
x: rect.x,
|
|
y: rect.y,
|
|
width: rect.width,
|
|
height: rect.height,
|
|
top: rect.top,
|
|
left: rect.left
|
|
},
|
|
// Add new readable formats
|
|
selector: createReadableSelector(element),
|
|
displayText: createElementDisplayText(element),
|
|
elementPath: getElementPath(element)
|
|
};
|
|
}
|
|
|
|
// Helper function to get element class name consistently
|
|
function getElementClassName(element) {
|
|
if (!element.className) return '';
|
|
|
|
if (typeof element.className === 'string') {
|
|
return element.className;
|
|
} else if (element.className.baseVal !== undefined) {
|
|
return element.className.baseVal;
|
|
} else {
|
|
return element.className.toString();
|
|
}
|
|
}
|
|
|
|
// Function to get element path (breadcrumb)
|
|
function getElementPath(element) {
|
|
const path = [];
|
|
let current = element;
|
|
|
|
while (current && current !== document.body && current !== document.documentElement) {
|
|
let pathSegment = current.tagName.toLowerCase();
|
|
|
|
if (current.id) {
|
|
pathSegment += `#${current.id}`;
|
|
} else if (current.className) {
|
|
const className = getElementClassName(current);
|
|
if (className.trim()) {
|
|
const firstClass = className.trim().split(/\s+/)[0];
|
|
pathSegment += `.${firstClass}`;
|
|
}
|
|
}
|
|
|
|
path.unshift(pathSegment);
|
|
current = current.parentElement;
|
|
|
|
// Limit path length
|
|
if (path.length >= 5) break;
|
|
}
|
|
|
|
return path.join(' > ');
|
|
}
|
|
|
|
// Event handlers
|
|
function handleMouseMove(e) {
|
|
if (!isInspectorActive) return;
|
|
|
|
const target = e.target;
|
|
if (!target || target === document.body || target === document.documentElement) return;
|
|
|
|
// Remove previous highlight
|
|
if (currentHighlight) {
|
|
currentHighlight.classList.remove('inspector-highlight');
|
|
}
|
|
|
|
// Add highlight to current element
|
|
target.classList.add('inspector-highlight');
|
|
currentHighlight = target;
|
|
|
|
const elementInfo = createElementInfo(target);
|
|
|
|
// Send message to parent
|
|
window.parent.postMessage({
|
|
type: 'INSPECTOR_HOVER',
|
|
elementInfo: elementInfo
|
|
}, '*');
|
|
}
|
|
|
|
function handleClick(e) {
|
|
if (!isInspectorActive) return;
|
|
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
const target = e.target;
|
|
if (!target || target === document.body || target === document.documentElement) return;
|
|
|
|
const elementInfo = createElementInfo(target);
|
|
|
|
// Send message to parent
|
|
window.parent.postMessage({
|
|
type: 'INSPECTOR_CLICK',
|
|
elementInfo: elementInfo
|
|
}, '*');
|
|
}
|
|
|
|
function handleMouseLeave() {
|
|
if (!isInspectorActive) return;
|
|
|
|
// Remove highlight
|
|
if (currentHighlight) {
|
|
currentHighlight.classList.remove('inspector-highlight');
|
|
currentHighlight = null;
|
|
}
|
|
|
|
// Send message to parent
|
|
window.parent.postMessage({
|
|
type: 'INSPECTOR_LEAVE'
|
|
}, '*');
|
|
}
|
|
|
|
// Function to activate/deactivate inspector
|
|
function setInspectorActive(active) {
|
|
isInspectorActive = active;
|
|
|
|
if (active) {
|
|
// Add inspector styles
|
|
if (!inspectorStyle) {
|
|
inspectorStyle = document.createElement('style');
|
|
inspectorStyle.textContent = `
|
|
.inspector-active * {
|
|
cursor: crosshair !important;
|
|
}
|
|
.inspector-highlight {
|
|
outline: 2px solid #3b82f6 !important;
|
|
outline-offset: -2px !important;
|
|
background-color: rgba(59, 130, 246, 0.1) !important;
|
|
}
|
|
`;
|
|
document.head.appendChild(inspectorStyle);
|
|
}
|
|
|
|
document.body.classList.add('inspector-active');
|
|
|
|
// Add event listeners
|
|
document.addEventListener('mousemove', handleMouseMove, true);
|
|
document.addEventListener('click', handleClick, true);
|
|
document.addEventListener('mouseleave', handleMouseLeave, true);
|
|
} else {
|
|
document.body.classList.remove('inspector-active');
|
|
|
|
// Remove highlight
|
|
if (currentHighlight) {
|
|
currentHighlight.classList.remove('inspector-highlight');
|
|
currentHighlight = null;
|
|
}
|
|
|
|
// Remove event listeners
|
|
document.removeEventListener('mousemove', handleMouseMove, true);
|
|
document.removeEventListener('click', handleClick, true);
|
|
document.removeEventListener('mouseleave', handleMouseLeave, true);
|
|
|
|
// Remove styles
|
|
if (inspectorStyle) {
|
|
inspectorStyle.remove();
|
|
inspectorStyle = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Listen for messages from parent
|
|
window.addEventListener('message', function(event) {
|
|
if (event.data.type === 'INSPECTOR_ACTIVATE') {
|
|
setInspectorActive(event.data.active);
|
|
}
|
|
});
|
|
|
|
// Auto-inject if inspector is already active
|
|
window.parent.postMessage({ type: 'INSPECTOR_READY' }, '*');
|
|
})(); |