fix: hover overlay ADD uses reliable image beacon and best image URL extraction
Browse files
src-tauri/resources/scripts/hover_overlay.js
CHANGED
|
@@ -1,144 +1,154 @@
|
|
| 1 |
-
/* Refstudio hover overlay: + ADD button
|
| 2 |
-
(function () {
|
| 3 |
-
if (window.
|
| 4 |
-
window.
|
| 5 |
-
|
| 6 |
-
var MIN = 90;
|
| 7 |
-
var activeImg = null;
|
| 8 |
-
var badge = null;
|
| 9 |
-
var showTimer = 0;
|
| 10 |
-
var hideTimer = 0;
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
s
|
| 16 |
-
(
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
function
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
if (!
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
}
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
var
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
badge.
|
| 104 |
-
badge.
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
var
|
| 133 |
-
if (
|
| 134 |
-
clearTimeout(
|
| 135 |
-
hideTimer
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
var
|
| 141 |
-
if (
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* Refstudio hover overlay: + ADD button on web images. */
|
| 2 |
+
(function () {
|
| 3 |
+
if (window.__muse_hover_v8) return;
|
| 4 |
+
window.__muse_hover_v8 = true;
|
| 5 |
+
|
| 6 |
+
var MIN = 90;
|
| 7 |
+
var activeImg = null;
|
| 8 |
+
var badge = null;
|
| 9 |
+
var showTimer = 0;
|
| 10 |
+
var hideTimer = 0;
|
| 11 |
+
var beaconKeepalive = [];
|
| 12 |
+
|
| 13 |
+
function injectCSS() {
|
| 14 |
+
if (document.getElementById('__muse_badge_css')) return;
|
| 15 |
+
var s = document.createElement('style'); s.id = '__muse_badge_css';
|
| 16 |
+
s.textContent = '#__muse_add_badge{all:initial;position:fixed;z-index:2147483647;display:flex;align-items:center;gap:4px;padding:7px 14px;background:rgba(255,255,255,0.97);color:#111;border-radius:999px;box-shadow:0 4px 20px rgba(0,0,0,0.3),0 0 0 1px rgba(0,0,0,0.05);font:700 11px/1 -apple-system,BlinkMacSystemFont,"Inter",sans-serif;cursor:pointer;opacity:0;transform:translateY(4px) scale(0.92);transition:opacity .15s,transform .15s;pointer-events:none;letter-spacing:0.02em;user-select:none}#__muse_add_badge.v{opacity:1;transform:none;pointer-events:auto}#__muse_add_badge:hover{background:#fff;box-shadow:0 6px 28px rgba(0,0,0,0.35),0 0 0 1px rgba(0,0,0,0.08);transform:scale(1.05)}#__muse_add_badge:active{transform:scale(0.94)}#__muse_add_badge svg{width:14px;height:14px;stroke-width:2.5}#__mt7{all:initial;position:fixed;left:50%;bottom:24px;transform:translateX(-50%) translateY(8px);z-index:2147483647;padding:9px 16px;border-radius:999px;background:rgba(20,20,20,.97);border:1px solid rgba(255,255,255,.1);box-shadow:0 10px 36px rgba(0,0,0,.5);font:600 12px/1 -apple-system,sans-serif;color:#fff;opacity:0;transition:opacity .16s,transform .16s;pointer-events:none}#__mt7.s{opacity:1;transform:translateX(-50%) translateY(0)}';
|
| 17 |
+
(document.head || document.documentElement).appendChild(s);
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
function enc(v) { return encodeURIComponent(v == null ? '' : String(v)); }
|
| 21 |
+
function absUrl(s) { try { return new URL(s || '', location.href).href; } catch (_) { return s || ''; } }
|
| 22 |
+
|
| 23 |
+
function bestFromSrcset(srcset) {
|
| 24 |
+
if (!srcset) return '';
|
| 25 |
+
try {
|
| 26 |
+
var best = '', bestScore = 0;
|
| 27 |
+
srcset.split(',').forEach(function(part) {
|
| 28 |
+
var bits = part.trim().split(/\s+/);
|
| 29 |
+
var u = bits[0];
|
| 30 |
+
var score = 1;
|
| 31 |
+
if (bits[1]) {
|
| 32 |
+
if (bits[1].endsWith('w')) score = parseInt(bits[1], 10) || 1;
|
| 33 |
+
else if (bits[1].endsWith('x')) score = (parseFloat(bits[1]) || 1) * 1000;
|
| 34 |
+
}
|
| 35 |
+
if (score > bestScore) { bestScore = score; best = u; }
|
| 36 |
+
});
|
| 37 |
+
return best ? absUrl(best) : '';
|
| 38 |
+
} catch (_) { return ''; }
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
function bestImageUrl(img) {
|
| 42 |
+
// Prefer the highest available URL. This catches srcset/lazy-loaded images.
|
| 43 |
+
var u = bestFromSrcset(img.getAttribute('srcset') || img.srcset);
|
| 44 |
+
if (u) return u;
|
| 45 |
+
u = img.getAttribute('data-full') || img.getAttribute('data-fullsrc') || img.getAttribute('data-original') || img.getAttribute('data-src') || img.getAttribute('data-lazy-src') || img.currentSrc || img.src;
|
| 46 |
+
u = absUrl(u);
|
| 47 |
+
// Site adapter tweaks for common thumb URL patterns.
|
| 48 |
+
try {
|
| 49 |
+
if (u.includes('pinimg.com') && u.match(/\/\d+x\//)) u = u.replace(/\/\d+x\//, '/originals/');
|
| 50 |
+
if (u.includes('artstation.com') && u.includes('/medium/')) u = u.replace('/medium/', '/large/');
|
| 51 |
+
if (u.includes('behance.net') && u.includes('/max_1200/')) u = u.replace('/max_1200/', '/max_3840/');
|
| 52 |
+
} catch (_) {}
|
| 53 |
+
return u;
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
function toast(m) {
|
| 57 |
+
var t = document.getElementById('__mt7');
|
| 58 |
+
if (!t) { t = document.createElement('div'); t.id = '__mt7'; document.documentElement.appendChild(t); }
|
| 59 |
+
t.textContent = m; t.classList.add('s');
|
| 60 |
+
clearTimeout(t._t); t._t = setTimeout(function () { t.classList.remove('s'); }, 1500);
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
function addToBoard(url, title, w, h) {
|
| 64 |
+
var u = 'muse-action://board?url=' + enc(url) + '&source=' + enc(location.href) + '&title=' + enc(title) + '&w=' + enc(w) + '&h=' + enc(h);
|
| 65 |
+
// Most reliable cross-origin child-webview bridge: image beacon to registered URI scheme.
|
| 66 |
+
var b = new Image();
|
| 67 |
+
b.onload = b.onerror = function () {
|
| 68 |
+
setTimeout(function () {
|
| 69 |
+
var i = beaconKeepalive.indexOf(b);
|
| 70 |
+
if (i >= 0) beaconKeepalive.splice(i, 1);
|
| 71 |
+
}, 500);
|
| 72 |
+
};
|
| 73 |
+
beaconKeepalive.push(b);
|
| 74 |
+
b.src = u;
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
function pos(el, img) {
|
| 78 |
+
var r = img.getBoundingClientRect();
|
| 79 |
+
var x = r.right - 78;
|
| 80 |
+
var y = r.top + 8;
|
| 81 |
+
if (x < r.left + 8) x = r.left + 8;
|
| 82 |
+
if (x > innerWidth - 90) x = innerWidth - 90;
|
| 83 |
+
if (y < 4) y = 4;
|
| 84 |
+
if (y > innerHeight - 42) y = innerHeight - 42;
|
| 85 |
+
el.style.left = x + 'px';
|
| 86 |
+
el.style.top = y + 'px';
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
function hide() {
|
| 90 |
+
if (!badge) return;
|
| 91 |
+
badge.classList.remove('v');
|
| 92 |
+
activeImg = null;
|
| 93 |
+
setTimeout(function () { if (badge && !badge.classList.contains('v')) badge.style.pointerEvents = 'none'; }, 160);
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
function show(img) {
|
| 97 |
+
if (img === activeImg && badge && badge.classList.contains('v')) return;
|
| 98 |
+
injectCSS();
|
| 99 |
+
activeImg = img;
|
| 100 |
+
if (!badge) {
|
| 101 |
+
badge = document.createElement('div');
|
| 102 |
+
badge.id = '__muse_add_badge';
|
| 103 |
+
badge.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg> ADD';
|
| 104 |
+
['mousedown','pointerdown','pointerup'].forEach(function(ev){ badge.addEventListener(ev, function(e){ e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); }, true); });
|
| 105 |
+
badge.addEventListener('click', function (e) {
|
| 106 |
+
e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation();
|
| 107 |
+
if (!activeImg) return;
|
| 108 |
+
var url = bestImageUrl(activeImg);
|
| 109 |
+
var title = activeImg.alt || activeImg.title || document.title || 'Reference';
|
| 110 |
+
var w = activeImg.naturalWidth || Math.round(activeImg.getBoundingClientRect().width);
|
| 111 |
+
var h = activeImg.naturalHeight || Math.round(activeImg.getBoundingClientRect().height);
|
| 112 |
+
if (!url || url.startsWith('data:') || url.startsWith('blob:')) { toast('Cannot capture this image'); return; }
|
| 113 |
+
addToBoard(url, title, w, h);
|
| 114 |
+
toast('✓ Added to Board');
|
| 115 |
+
}, true);
|
| 116 |
+
badge.onmouseenter = function () { clearTimeout(hideTimer); };
|
| 117 |
+
badge.onmouseleave = function () { hideTimer = setTimeout(hide, 300); };
|
| 118 |
+
document.documentElement.appendChild(badge);
|
| 119 |
+
}
|
| 120 |
+
pos(badge, activeImg);
|
| 121 |
+
badge.offsetHeight;
|
| 122 |
+
badge.classList.add('v');
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
function isValidTarget(el) {
|
| 126 |
+
if (!el || el.tagName !== 'IMG') return false;
|
| 127 |
+
var r = el.getBoundingClientRect();
|
| 128 |
+
return r.width >= MIN && r.height >= MIN;
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
document.addEventListener('mouseover', function (e) {
|
| 132 |
+
var t = e.target;
|
| 133 |
+
if (!isValidTarget(t)) return;
|
| 134 |
+
if (t === activeImg) { clearTimeout(hideTimer); return; }
|
| 135 |
+
clearTimeout(hideTimer); clearTimeout(showTimer);
|
| 136 |
+
showTimer = setTimeout(function () { show(t); }, 180);
|
| 137 |
+
}, true);
|
| 138 |
+
|
| 139 |
+
document.addEventListener('mouseout', function (e) {
|
| 140 |
+
var t = e.target;
|
| 141 |
+
if (!t || t.tagName !== 'IMG' || t !== activeImg) return;
|
| 142 |
+
var rel = e.relatedTarget;
|
| 143 |
+
if (badge && (badge === rel || badge.contains && badge.contains(rel))) return;
|
| 144 |
+
clearTimeout(showTimer);
|
| 145 |
+
hideTimer = setTimeout(hide, 300);
|
| 146 |
+
}, true);
|
| 147 |
+
|
| 148 |
+
window.addEventListener('scroll', function () {
|
| 149 |
+
if (!badge || !activeImg) return;
|
| 150 |
+
var r = activeImg.getBoundingClientRect();
|
| 151 |
+
if (r.bottom < -20 || r.top > innerHeight + 20) hide();
|
| 152 |
+
else pos(badge, activeImg);
|
| 153 |
+
}, { passive: true, capture: true });
|
| 154 |
+
})();
|