Merge version_1 into main #4
@@ -92,7 +92,9 @@ export default function RootLayout({
|
||||
' background-color: #4d96ff05 !important;' +
|
||||
'}' +
|
||||
'img.webild-hover,' +
|
||||
'img.webild-selected {' +
|
||||
'img.webild-selected,' +
|
||||
'video.webild-hover,' +
|
||||
'video.webild-selected {' +
|
||||
' outline-offset: 2px !important;' +
|
||||
'}' +
|
||||
'.webild-element-type-label {' +
|
||||
@@ -164,6 +166,10 @@ export default function RootLayout({
|
||||
return 'Image';
|
||||
}
|
||||
|
||||
if (tagName === 'video') {
|
||||
return 'Video';
|
||||
}
|
||||
|
||||
const backgroundImage = computedStyle.backgroundImage;
|
||||
if (backgroundImage && backgroundImage !== 'none') {
|
||||
const urlMatch = backgroundImage.match(/url(['"]?([^'")]+)['"]?)/);
|
||||
@@ -246,6 +252,40 @@ export default function RootLayout({
|
||||
return url;
|
||||
};
|
||||
|
||||
const getMediaTypeFromUrl = (url) => {
|
||||
const videoExts = ['.mp4', '.webm', '.ogg', '.mov', '.avi', '.mkv', '.m4v', '.wmv'];
|
||||
const imageExts = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg', '.bmp', '.ico', '.tiff', '.avif'];
|
||||
try {
|
||||
const pathname = new URL(url).pathname.toLowerCase();
|
||||
if (videoExts.some(function(ext) { return pathname.endsWith(ext); })) return 'video';
|
||||
if (imageExts.some(function(ext) { return pathname.endsWith(ext); })) return 'image';
|
||||
} catch(e) {}
|
||||
return 'unknown';
|
||||
};
|
||||
|
||||
const swapMediaElement = (oldEl, newTag, newSrc) => {
|
||||
const newEl = document.createElement(newTag);
|
||||
// Copy all attributes (preserves class, id, style, data-webild-id, etc.)
|
||||
Array.from(oldEl.attributes).forEach(function(attr) {
|
||||
if (attr.name !== 'src' && attr.name !== 'alt' && attr.name !== 'srcset' && attr.name !== 'autoplay' && attr.name !== 'loop' && attr.name !== 'muted' && attr.name !== 'playsinline') {
|
||||
try { newEl.setAttribute(attr.name, attr.value); } catch(e) {}
|
||||
}
|
||||
});
|
||||
newEl.style.cssText = oldEl.style.cssText;
|
||||
if (newTag === 'video') {
|
||||
newEl.setAttribute('autoplay', '');
|
||||
newEl.setAttribute('loop', '');
|
||||
newEl.setAttribute('muted', '');
|
||||
newEl.setAttribute('playsinline', '');
|
||||
}
|
||||
newEl.src = newSrc;
|
||||
if (oldEl.parentNode) {
|
||||
oldEl.parentNode.replaceChild(newEl, oldEl);
|
||||
}
|
||||
console.log('[Webild] swapped <' + oldEl.tagName.toLowerCase() + '> -> <' + newTag + '>', newSrc);
|
||||
return newEl;
|
||||
};
|
||||
|
||||
const getElementInfo = (element, assignId = false) => {
|
||||
const rect = element.getBoundingClientRect();
|
||||
const tagName = element.tagName.toLowerCase();
|
||||
@@ -288,7 +328,19 @@ export default function RootLayout({
|
||||
isBackground: false
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
if (tagName === 'video') {
|
||||
const rawSrc = element.src || element.currentSrc || (element.querySelector('source') && element.querySelector('source').src) || '';
|
||||
const resolvedSrc = extractOriginalUrl(rawSrc);
|
||||
console.log('[Webild] getElementInfo video: rawSrc=', rawSrc, 'resolvedSrc=', resolvedSrc);
|
||||
info.imageData = {
|
||||
src: resolvedSrc,
|
||||
alt: element.getAttribute('aria-label') || undefined,
|
||||
isBackground: false,
|
||||
isVideo: true
|
||||
};
|
||||
}
|
||||
|
||||
const computedStyle = window.getComputedStyle(element);
|
||||
const backgroundImage = computedStyle.backgroundImage;
|
||||
if (backgroundImage && backgroundImage !== 'none') {
|
||||
@@ -336,7 +388,8 @@ export default function RootLayout({
|
||||
const tagName = element.tagName?.toLowerCase();
|
||||
if (invalidElements.includes(tagName)) return false;
|
||||
const isImage = tagName === 'img';
|
||||
if (isImage) return true;
|
||||
const isVideo = tagName === 'video';
|
||||
if (isImage || isVideo) return true;
|
||||
const hasInnerHTML = element.innerHTML && element.innerHTML.trim().length > 0;
|
||||
const hasTextContent = element.textContent && element.textContent.trim().length > 0;
|
||||
const hasChildren = element.children && element.children.length > 0;
|
||||
@@ -376,7 +429,7 @@ export default function RootLayout({
|
||||
node.nodeType === Node.TEXT_NODE && node.textContent && node.textContent.trim().length > 0
|
||||
);
|
||||
|
||||
const hasImages = element.tagName === 'IMG' || computedStyle.backgroundImage !== 'none' || element.querySelector('img');
|
||||
const hasImages = element.tagName === 'IMG' || element.tagName === 'VIDEO' || computedStyle.backgroundImage !== 'none' || element.querySelector('img') || element.querySelector('video');
|
||||
const isInteractive = ['BUTTON', 'A', 'INPUT', 'SELECT', 'TEXTAREA'].includes(element.tagName);
|
||||
const hasFewChildren = element.children.length <= 3;
|
||||
const area = rect.width * rect.height;
|
||||
@@ -1057,11 +1110,22 @@ export default function RootLayout({
|
||||
updateButtonText(element, change.oldValue);
|
||||
}
|
||||
} else if (change.type === 'replaceImage') {
|
||||
const isBackground = element.tagName.toLowerCase() !== 'img';
|
||||
const revertTag = element.tagName.toLowerCase();
|
||||
const isBackground = revertTag !== 'img' && revertTag !== 'video';
|
||||
if (isBackground) {
|
||||
element.style.backgroundImage = change.oldValue ? 'url(' + change.oldValue + ')' : '';
|
||||
} else {
|
||||
element.src = change.oldValue;
|
||||
const oldMediaType = getMediaTypeFromUrl(change.oldValue);
|
||||
if (revertTag === 'video' && oldMediaType === 'image') {
|
||||
swapMediaElement(element, 'img', change.oldValue);
|
||||
} else if (revertTag === 'img' && oldMediaType === 'video') {
|
||||
swapMediaElement(element, 'video', change.oldValue);
|
||||
} else if (revertTag === 'video') {
|
||||
element.src = change.oldValue;
|
||||
element.load();
|
||||
} else {
|
||||
element.src = change.oldValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -1161,9 +1225,11 @@ export default function RootLayout({
|
||||
if (!isActive) return;
|
||||
|
||||
if (e.data.type === 'webild-replace-image') {
|
||||
const { selector, newSrc, isBackground } = e.data.data;
|
||||
const { selector, newSrc, isBackground, allowMediaTypeSwap } = e.data.data;
|
||||
let element = null;
|
||||
|
||||
console.log('[Webild] webild-replace-image received:', { selector, newSrc, isBackground, allowMediaTypeSwap });
|
||||
|
||||
try {
|
||||
element = document.querySelector(selector);
|
||||
} catch {
|
||||
@@ -1175,6 +1241,7 @@ export default function RootLayout({
|
||||
}
|
||||
|
||||
if (!element) {
|
||||
console.warn('[Webild] element not found for selector:', selector);
|
||||
window.parent.postMessage({
|
||||
type: 'webild-image-replacement-error',
|
||||
data: { selector, message: 'Element not found', success: false }
|
||||
@@ -1182,20 +1249,51 @@ export default function RootLayout({
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[Webild] element found:', element.tagName, 'isBackground:', isBackground);
|
||||
|
||||
try {
|
||||
let replaced = false;
|
||||
let oldValue = '';
|
||||
|
||||
if (isBackground) {
|
||||
oldValue = window.getComputedStyle(element).backgroundImage;
|
||||
console.log('[Webild] replacing background-image, oldValue:', oldValue);
|
||||
element.style.backgroundImage = \`url('\${newSrc}')\`;
|
||||
replaced = true;
|
||||
} else if (element.tagName.toLowerCase() === 'img') {
|
||||
oldValue = element.src;
|
||||
element.src = newSrc;
|
||||
const newMediaType = getMediaTypeFromUrl(newSrc);
|
||||
console.log('[Webild] replacing <img> src, oldValue:', oldValue, '-> newSrc:', newSrc, 'newMediaType:', newMediaType, 'allowMediaTypeSwap:', allowMediaTypeSwap);
|
||||
if (newMediaType === 'video' && allowMediaTypeSwap) {
|
||||
const swapped = swapMediaElement(element, 'video', newSrc);
|
||||
if (selectedElement === element) selectedElement = swapped;
|
||||
element = swapped;
|
||||
} else {
|
||||
element.src = newSrc;
|
||||
}
|
||||
replaced = true;
|
||||
} else if (element.tagName.toLowerCase() === 'video') {
|
||||
oldValue = element.src || element.currentSrc || '';
|
||||
const newMediaType = getMediaTypeFromUrl(newSrc);
|
||||
const sources = element.querySelectorAll('source');
|
||||
console.log('[Webild] replacing <video> src, oldValue:', oldValue, '-> newSrc:', newSrc, 'newMediaType:', newMediaType, 'allowMediaTypeSwap:', allowMediaTypeSwap, 'sources count:', sources.length);
|
||||
if (newMediaType === 'image' && allowMediaTypeSwap) {
|
||||
const swapped = swapMediaElement(element, 'img', newSrc);
|
||||
if (selectedElement === element) selectedElement = swapped;
|
||||
element = swapped;
|
||||
} else {
|
||||
if (sources.length > 0) {
|
||||
sources.forEach(function(source) { source.src = newSrc; });
|
||||
element.load();
|
||||
} else {
|
||||
element.src = newSrc;
|
||||
element.load();
|
||||
}
|
||||
}
|
||||
replaced = true;
|
||||
} else {
|
||||
const hasBackgroundImage = window.getComputedStyle(element).backgroundImage !== 'none';
|
||||
console.log('[Webild] element is <' + element.tagName.toLowerCase() + '>, hasBackgroundImage:', hasBackgroundImage);
|
||||
if (hasBackgroundImage) {
|
||||
oldValue = window.getComputedStyle(element).backgroundImage;
|
||||
element.style.backgroundImage = \`url('\${newSrc}')\`;
|
||||
@@ -1215,6 +1313,7 @@ export default function RootLayout({
|
||||
}
|
||||
|
||||
cleanOldValue = extractOriginalUrl(cleanOldValue);
|
||||
console.log('[Webild] replacement done, cleanOldValue:', cleanOldValue, 'newSrc:', newSrc);
|
||||
|
||||
const change = {
|
||||
type: 'replaceImage',
|
||||
|
||||
@@ -64,7 +64,7 @@ export default function LandingPage() {
|
||||
id: "1", name: "Classic Ceramic Coffee Cup", price: "$24.99", variant: "White • 10oz • 5 Colors", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_34yEatLQt6B9A82mZgfqKsKaLO9/uploaded-1765990781008-6ywkyb3c.jpg", imageAlt: "Classic white ceramic coffee cup"
|
||||
},
|
||||
{
|
||||
id: "2", name: "Premium Travel Thermos", price: "$39.99", variant: "Stainless Steel • Insulated • 16oz", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_34yEatLQt6B9A82mZgfqKsKaLO9/uploaded-1771414071309-lllrolfa.mp4", imageAlt: "Insulated travel thermos cup"
|
||||
id: "2", name: "Premium Travel Thermos", price: "$39.99", variant: "Stainless Steel • Insulated • 16oz", imageSrc: "https://webuild-dev.s3.eu-north-1.amazonaws.com/users/user_34yEatLQt6B9A82mZgfqKsKaLO9/uploaded-1765990781008-6ywkyb3c.jpg", imageAlt: "Insulated travel thermos cup"
|
||||
},
|
||||
{
|
||||
id: "3", name: "Vibrant Artisan Mug", price: "$28.99", variant: "Hand-painted • Ceramic • 12oz", imageSrc: "https://img.b2bpic.net/free-photo/cup-coffee_1339-7154.jpg", imageAlt: "Colorful hand-painted artisan mug"
|
||||
|
||||
Reference in New Issue
Block a user