asdf98 commited on
Commit
5b719d3
·
verified ·
1 Parent(s): 3fdb1ce

fix: hover ADD fires only ONE action path (navigation), remove duplicate handler in commands.rs

Browse files
src-tauri/resources/scripts/hover_overlay.js CHANGED
@@ -1,47 +1,22 @@
1
  /* Refstudio hover overlay: + ADD button on web images.
2
- * v10fixes: badge disappears before click, badge not clickable, click goes to image behind.
3
- * Root causes fixed:
4
- * 1. Badge was positioned at top-right of image but mouseout fired when moving FROM image TO badge
5
- * 2. Badge pointer-events were toggled too aggressively
6
- * 3. Badge hide timer was too short (300ms) — cursor couldn't reach it
7
  */
8
  (function () {
9
- if (window.__muse_hover_v10) return;
10
- window.__muse_hover_v10 = true;
11
 
12
  var MIN = 90;
13
  var activeImg = null;
14
  var badge = null;
15
  var showTimer = 0;
16
  var hideTimer = 0;
 
17
 
18
  function injectCSS() {
19
- if (document.getElementById('__muse_badge_css2')) return;
20
- var s = document.createElement('style'); s.id = '__muse_badge_css2';
21
- s.textContent = [
22
- /* The badge itself */
23
- '#__muse_add_badge{',
24
- ' all:initial;position:fixed;z-index:2147483647;',
25
- ' display:flex;align-items:center;gap:5px;',
26
- ' padding:8px 16px;',
27
- ' background:rgba(10,132,255,0.95);color:#fff;',
28
- ' border-radius:999px;',
29
- ' box-shadow:0 4px 20px rgba(0,0,0,0.4),0 0 0 1px rgba(255,255,255,0.1);',
30
- ' font:700 12px/1 -apple-system,BlinkMacSystemFont,"Inter",sans-serif;',
31
- ' cursor:pointer;',
32
- ' opacity:0;transform:translateY(4px) scale(0.92);',
33
- ' transition:opacity .2s,transform .2s,background .1s;',
34
- ' pointer-events:auto;', /* ALWAYS clickable when visible */
35
- ' letter-spacing:0.01em;user-select:none;',
36
- '}',
37
- '#__muse_add_badge.v{opacity:1;transform:none}',
38
- '#__muse_add_badge:hover{background:rgba(10,132,255,1);box-shadow:0 6px 28px rgba(10,132,255,0.4),0 0 0 1px rgba(255,255,255,0.2);transform:scale(1.06)}',
39
- '#__muse_add_badge:active{transform:scale(0.92);background:rgba(10,100,220,1)}',
40
- '#__muse_add_badge svg{width:14px;height:14px;stroke-width:2.5;pointer-events:none}',
41
- /* Toast */
42
- '#__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}',
43
- '#__mt7.s{opacity:1;transform:translateX(-50%) translateY(0)}'
44
- ].join('');
45
  (document.head || document.documentElement).appendChild(s);
46
  }
47
 
@@ -49,134 +24,52 @@
49
  function absUrl(s) { try { return new URL(s || '', location.href).href; } catch (_) { return s || ''; } }
50
 
51
  function bestImageUrl(img) {
52
- // srcset best
53
  var srcset = img.getAttribute('srcset') || '';
54
- if (srcset) {
55
- var best = '', bestScore = 0;
56
- srcset.split(',').forEach(function(part) {
57
- var bits = part.trim().split(/\s+/); var u = bits[0]; var score = 1;
58
- if (bits[1]) { if (bits[1].endsWith('w')) score = parseInt(bits[1],10)||1; else if (bits[1].endsWith('x')) score = (parseFloat(bits[1])||1)*1000; }
59
- if (score > bestScore) { bestScore = score; best = u; }
60
- });
61
- if (best) return absUrl(best);
62
- }
63
- var 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;
64
  u = absUrl(u);
65
- try {
66
- if (u.includes('pinimg.com') && u.match(/\/\d+x\//)) u = u.replace(/\/\d+x\//, '/originals/');
67
- if (u.includes('artstation.com') && u.includes('/medium/')) u = u.replace('/medium/', '/large/');
68
- if (u.includes('behance.net') && u.includes('/max_1200/')) u = u.replace('/max_1200/', '/max_3840/');
69
- } catch (_) {}
70
  return u;
71
  }
72
 
73
- function toast(m) {
74
- var t = document.getElementById('__mt7');
75
- if (!t) { t = document.createElement('div'); t.id = '__mt7'; document.documentElement.appendChild(t); }
76
- t.textContent = m; t.classList.add('s');
77
- clearTimeout(t._t); t._t = setTimeout(function () { t.classList.remove('s'); }, 2000);
78
- }
79
 
80
  function fireAction(url, title, w, h) {
81
- var u = 'muse-action://board?url=' + enc(url) + '&source=' + enc(location.href) + '&title=' + enc(title) + '&w=' + enc(w) + '&h=' + enc(h);
82
- // Fire through all three channels for maximum reliability.
83
- try { window.location.href = u; } catch (_) {}
84
- try { var b = new Image(); b.src = u; } catch (_) {}
85
- try { fetch(u).catch(function(){}); } catch (_) {}
86
- }
87
-
88
- function clearHide() { clearTimeout(hideTimer); }
89
- function scheduleHide() { clearHide(); hideTimer = setTimeout(hide, 600); }
90
-
91
- function hide() {
92
- if (!badge) return;
93
- badge.classList.remove('v');
94
- activeImg = null;
95
  }
96
 
97
- function pos(el, img) {
98
- var r = img.getBoundingClientRect();
99
- // Position badge at TOP-RIGHT of image, inside the image bounds
100
- var x = r.right - 80;
101
- var y = r.top + 10;
102
- if (x < r.left + 4) x = r.left + 4;
103
- if (x > window.innerWidth - 90) x = window.innerWidth - 90;
104
- if (y < 4) y = 4;
105
- if (y > window.innerHeight - 40) y = window.innerHeight - 40;
106
- el.style.left = x + 'px';
107
- el.style.top = y + 'px';
108
- }
109
 
110
- function show(img) {
111
- if (img === activeImg && badge && badge.classList.contains('v')) return;
112
- injectCSS();
113
- activeImg = img;
114
- if (!badge) {
115
- badge = document.createElement('div');
116
- badge.id = '__muse_add_badge';
117
- 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';
118
 
119
- // Click handler — the actual capture action
120
- badge.addEventListener('click', function (e) {
121
- e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation();
122
- if (!activeImg) return;
123
- var url = bestImageUrl(activeImg);
124
- if (!url || url.startsWith('data:') || url.startsWith('blob:')) { toast('Cannot capture this image'); return; }
125
- var title = activeImg.alt || activeImg.title || document.title || 'Reference';
126
- var w = activeImg.naturalWidth || Math.round(activeImg.getBoundingClientRect().width);
127
- var h = activeImg.naturalHeight || Math.round(activeImg.getBoundingClientRect().height);
128
- fireAction(url, title, w, h);
129
- toast('✓ Added to Board');
130
- hide();
131
- }, true);
132
-
133
- // Block ALL events from reaching elements underneath
134
- ['mousedown','mouseup','pointerdown','pointerup','touchstart','touchend'].forEach(function(ev) {
135
- badge.addEventListener(ev, function(e) { e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); }, true);
136
- });
137
-
138
- // Keep badge visible while hovering it
139
- badge.addEventListener('mouseenter', clearHide, true);
140
- badge.addEventListener('mouseleave', scheduleHide, true);
141
 
 
 
 
 
 
 
 
 
 
 
142
  document.documentElement.appendChild(badge);
143
  }
144
- pos(badge, activeImg);
145
- void badge.offsetHeight; // force reflow for transition
146
- badge.classList.add('v');
147
  }
148
 
149
- function isValidTarget(el) {
150
- if (!el || el.tagName !== 'IMG') return false;
151
- if (el === badge) return false;
152
- var r = el.getBoundingClientRect();
153
- return r.width >= MIN && r.height >= MIN;
154
- }
155
-
156
- document.addEventListener('mouseover', function (e) {
157
- var t = e.target;
158
- // If hovering badge, keep it alive
159
- if (badge && (t === badge || badge.contains(t))) { clearHide(); return; }
160
- if (!isValidTarget(t)) return;
161
- if (t === activeImg) { clearHide(); return; }
162
- clearHide(); clearTimeout(showTimer);
163
- showTimer = setTimeout(function () { show(t); }, 150);
164
- }, true);
165
-
166
- document.addEventListener('mouseout', function (e) {
167
- var t = e.target;
168
- if (!t || t.tagName !== 'IMG' || t !== activeImg) return;
169
- var rel = e.relatedTarget;
170
- // Don't hide if moving to the badge
171
- if (badge && rel && (badge === rel || badge.contains(rel))) { clearHide(); return; }
172
- clearTimeout(showTimer);
173
- scheduleHide();
174
- }, true);
175
 
176
- window.addEventListener('scroll', function () {
177
- if (!badge || !activeImg) return;
178
- var r = activeImg.getBoundingClientRect();
179
- if (r.bottom < -20 || r.top > window.innerHeight + 20) hide();
180
- else pos(badge, activeImg);
181
- }, { passive: true, capture: true });
182
  })();
 
1
  /* Refstudio hover overlay: + ADD button on web images.
2
+ * v11fires ONLY through window.location.href (on_navigation handler).
3
+ * Previous versions fired 3 channels causing duplicate images.
 
 
 
4
  */
5
  (function () {
6
+ if (window.__muse_hover_v11) return;
7
+ window.__muse_hover_v11 = true;
8
 
9
  var MIN = 90;
10
  var activeImg = null;
11
  var badge = null;
12
  var showTimer = 0;
13
  var hideTimer = 0;
14
+ var capturing = false; // prevent double-click
15
 
16
  function injectCSS() {
17
+ if (document.getElementById('__muse_badge_css3')) return;
18
+ var s = document.createElement('style'); s.id = '__muse_badge_css3';
19
+ s.textContent = '#__muse_add_badge{all:initial;position:fixed;z-index:2147483647;display:flex;align-items:center;gap:5px;padding:8px 16px;background:rgba(10,132,255,0.95);color:#fff;border-radius:999px;box-shadow:0 4px 20px rgba(0,0,0,0.4),0 0 0 1px rgba(255,255,255,0.1);font:700 12px/1 -apple-system,BlinkMacSystemFont,"Inter",sans-serif;cursor:pointer;opacity:0;transform:translateY(4px) scale(0.92);transition:opacity .2s,transform .2s,background .1s;pointer-events:auto;letter-spacing:0.01em;user-select:none}#__muse_add_badge.v{opacity:1;transform:none}#__muse_add_badge:hover{background:rgba(10,132,255,1);box-shadow:0 6px 28px rgba(10,132,255,0.4),0 0 0 1px rgba(255,255,255,0.2);transform:scale(1.06)}#__muse_add_badge:active{transform:scale(0.92);background:rgba(10,100,220,1)}#__muse_add_badge svg{width:14px;height:14px;stroke-width:2.5;pointer-events:none}#__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)}';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  (document.head || document.documentElement).appendChild(s);
21
  }
22
 
 
24
  function absUrl(s) { try { return new URL(s || '', location.href).href; } catch (_) { return s || ''; } }
25
 
26
  function bestImageUrl(img) {
 
27
  var srcset = img.getAttribute('srcset') || '';
28
+ if (srcset) { var best='',bs=0; srcset.split(',').forEach(function(p){var b=p.trim().split(/\s+/),u=b[0],sc=1;if(b[1]){if(b[1].endsWith('w'))sc=parseInt(b[1],10)||1;else if(b[1].endsWith('x'))sc=(parseFloat(b[1])||1)*1000;}if(sc>bs){bs=sc;best=u;}}); if(best) return absUrl(best); }
29
+ var u = img.getAttribute('data-full')||img.getAttribute('data-original')||img.getAttribute('data-src')||img.getAttribute('data-lazy-src')||img.currentSrc||img.src;
 
 
 
 
 
 
 
 
30
  u = absUrl(u);
31
+ try { if(u.includes('pinimg.com')&&u.match(/\/\d+x\//))u=u.replace(/\/\d+x\//,'/originals/'); if(u.includes('artstation.com')&&u.includes('/medium/'))u=u.replace('/medium/','/large/'); } catch(_){}
 
 
 
 
32
  return u;
33
  }
34
 
35
+ function toast(m) { var t=document.getElementById('__mt7'); if(!t){t=document.createElement('div');t.id='__mt7';document.documentElement.appendChild(t);} t.textContent=m;t.classList.add('s'); clearTimeout(t._t);t._t=setTimeout(function(){t.classList.remove('s');},2000); }
 
 
 
 
 
36
 
37
  function fireAction(url, title, w, h) {
38
+ if (capturing) return;
39
+ capturing = true;
40
+ // ONLY use window.location.href Tauri on_navigation will intercept and return false.
41
+ // This is the single reliable path. Do NOT also fire beacon/fetch (causes duplicates).
42
+ var u = 'muse-action://board?url='+enc(url)+'&source='+enc(location.href)+'&title='+enc(title)+'&w='+enc(w)+'&h='+enc(h);
43
+ window.location.href = u;
44
+ // Reset after short delay (the page won't actually navigate since Rust returns false)
45
+ setTimeout(function(){ capturing = false; }, 1000);
 
 
 
 
 
 
46
  }
47
 
48
+ function clearHide(){clearTimeout(hideTimer);}
49
+ function scheduleHide(){clearHide();hideTimer=setTimeout(hide,600);}
 
 
 
 
 
 
 
 
 
 
50
 
51
+ function hide(){if(!badge)return;badge.classList.remove('v');activeImg=null;}
 
 
 
 
 
 
 
52
 
53
+ function pos(el,img){var r=img.getBoundingClientRect();var x=r.right-80,y=r.top+10;if(x<r.left+4)x=r.left+4;if(x>innerWidth-90)x=innerWidth-90;if(y<4)y=4;if(y>innerHeight-40)y=innerHeight-40;el.style.left=x+'px';el.style.top=y+'px';}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
 
55
+ function show(img){
56
+ if(img===activeImg&&badge&&badge.classList.contains('v'))return;
57
+ injectCSS(); activeImg=img;
58
+ if(!badge){
59
+ badge=document.createElement('div');badge.id='__muse_add_badge';
60
+ 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';
61
+ badge.addEventListener('click',function(e){e.preventDefault();e.stopPropagation();e.stopImmediatePropagation();if(!activeImg)return;var url=bestImageUrl(activeImg);if(!url||url.startsWith('data:')||url.startsWith('blob:')){toast('Cannot capture');return;}fireAction(url,activeImg.alt||activeImg.title||document.title||'Reference',activeImg.naturalWidth||Math.round(activeImg.getBoundingClientRect().width),activeImg.naturalHeight||Math.round(activeImg.getBoundingClientRect().height));toast('✓ Added to Board');hide();},true);
62
+ ['mousedown','mouseup','pointerdown','pointerup','touchstart','touchend'].forEach(function(ev){badge.addEventListener(ev,function(e){e.preventDefault();e.stopPropagation();e.stopImmediatePropagation();},true);});
63
+ badge.addEventListener('mouseenter',clearHide,true);
64
+ badge.addEventListener('mouseleave',scheduleHide,true);
65
  document.documentElement.appendChild(badge);
66
  }
67
+ pos(badge,activeImg);void badge.offsetHeight;badge.classList.add('v');
 
 
68
  }
69
 
70
+ function isValid(el){if(!el||el.tagName!=='IMG')return false;if(el===badge)return false;var r=el.getBoundingClientRect();return r.width>=MIN&&r.height>=MIN;}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
 
72
+ document.addEventListener('mouseover',function(e){var t=e.target;if(badge&&(t===badge||badge.contains(t))){clearHide();return;}if(!isValid(t))return;if(t===activeImg){clearHide();return;}clearHide();clearTimeout(showTimer);showTimer=setTimeout(function(){show(t);},150);},true);
73
+ document.addEventListener('mouseout',function(e){var t=e.target;if(!t||t.tagName!=='IMG'||t!==activeImg)return;var rel=e.relatedTarget;if(badge&&rel&&(badge===rel||badge.contains(rel))){clearHide();return;}clearTimeout(showTimer);scheduleHide();},true);
74
+ window.addEventListener('scroll',function(){if(!badge||!activeImg)return;var r=activeImg.getBoundingClientRect();if(r.bottom<-20||r.top>innerHeight+20)hide();else pos(badge,activeImg);},{passive:true,capture:true});
 
 
 
75
  })();