mirror of
https://github.com/Matysh/houseplan-card
synced 2026-07-31 16:38:31 +00:00
The fit overlay is actually touchable
The devlayer is pointer-events: none and every child opts back in; the overlay never did, so every real click fell through to the plan while the smoke's synthetic dispatch — which skips hit-testing — kept passing. The overlay now opts in, the corner handles grew to finger size, and the smoke performs REAL elementFromPoint hit-tests through the shadow root so an untouchable overlay can never pass again.
This commit is contained in:
@@ -1318,6 +1318,11 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow
|
||||
overflow: visible;
|
||||
touch-action: none;
|
||||
cursor: grab;
|
||||
/* the devlayer is pointer-events: none and every child opts back in —
|
||||
without this line real clicks flew straight through the overlay
|
||||
(owner: «уголки не кликабельны»; synthetic smoke events bypass
|
||||
hit-testing, which is why they lied) */
|
||||
pointer-events: auto;
|
||||
}
|
||||
.vacfit:active { cursor: grabbing; }
|
||||
.vacfit polygon {
|
||||
@@ -2176,7 +2181,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow
|
||||
${a.length?B`<div class="rhint">${ti(this._t("vac.cal_maps"),{maps:a.join(", ")})}</div>`:G}
|
||||
`:G}
|
||||
</div>`}_vacMapId(t,e){if("default"!==e.mapId)return e.mapId;const i=this._vacEntity(t),s=i?this.hass?.states[i]?.attributes?.selected_map:null;return s?String(s):"default"}_vacSaveMatrix(t,e,i,s){const o=this._serverCfg,n=o?.markers?.find(e=>e.id===t);if(!n)return;const r={...n.vacuum||{}};r.source=e,r.calibration={...r.calibration||{},[i]:s.map(t=>Number(t.toFixed(6)))},n.vacuum=r,this._regSignature="",this._saveConfig(),this.requestUpdate()}_vacAutoCalibrate(t){const e=this._vacSource(t),i=e?zi(this.hass?.states[e]?.attributes):null;if(!e||!i||i.rooms.length<3)return void this._showToast(this._t("vac.autocal_no_rooms"));const s=this._spaceModel(t.space),o=(s?.rooms||[]).filter(t=>t.name&&t.poly?.length>=3).map(t=>{const e=Ae(t.poly);return{name:t.name,cx:e[0],cy:e[1]}}),n=Ai(i.rooms,o);n?(n.residual>50&&this._showToast(ti(this._t("vac.autocal_res_warn"),{rooms:String(n.matched.length)})),this._vacSaveMatrix(t.id,e,this._vacMapId(t,i),n.matrix),this._showToast(ti(this._t("vac.autocal_done"),{rooms:String(n.matched.length)}))):this._showToast(this._t("vac.autocal_no_match"))}_vacStartFit(t){const e=this._vacSource(t),i=e?zi(this.hass?.states[e]?.attributes):null;if(!e||!i)return void this._showToast(this._t("vac.cal_need_pos"));const s=this._vacMapId(t,i),o=t.marker?.vacuum?.calibration?.[s],n=this._spaceModel(t.space),r=n?.vb||[0,0,Ds,Ds],a=o&&6===o.length&&function(t){const e=t[0]*t[4]-t[1]*t[3];if(!Number.isFinite(e)||Math.abs(e)<1e-12)return null;const i=e<0,s=Math.sqrt(Math.abs(e));let o=180*Math.atan2(-t[1],t[4])/Math.PI;return o=(90*Math.round(o/90)%360+360)%360,{ox:t[2],oy:t[5],s:s,rot:o,mir:i}}(o)||function(t,e){const i=[],s=[];for(const e of t)null!=e.x0?(i.push(e.x0,e.x1),s.push(e.y0,e.y1)):(i.push(e.cx),s.push(e.cy));if(!i.length)return{ox:e[0]+e[2]/2,oy:e[1]+e[3]/2,s:e[2]/1e4,rot:0,mir:!0};const o=Math.min(...i),n=Math.max(...i),r=Math.min(...s),a=Math.max(...s),l=Math.max(n-o,a-r)||1,c={ox:0,oy:0,s:.6*Math.min(e[2],e[3])/l,rot:0,mir:!0},h=Ii(c),[d,p]=Ci(h,(o+n)/2,(r+a)/2);return c.ox=e[0]+e[2]/2-d,c.oy=e[1]+e[3]/2-p,c}(i.rooms,r);this._markerDialog=null,t.space!==this._space&&(this._space=t.space),this._vacFit={markerId:t.id,source:e,mapId:s,p:a,drag:null}}_vacFitSave(){const t=this._vacFit;t&&(this._vacSaveMatrix(t.markerId,t.source,t.mapId,Ii(t.p)),this._vacFit=null,this._showToast(this._t("vac.cal_done")))}_vacFitTurn(t){const e=this._vacFit;if(!e)return;const i=zi(this.hass?.states[e.source]?.attributes),s=this._vacGhostCentre(i?.rooms||[]),o={...e.p,...t};this._vacFit={...e,p:Li(o,e.p,s[0],s[1])}}_vacGhostCentre(t){const e=[],i=[];for(const s of t)e.push(s.x0??s.cx,s.x1??s.cx),i.push(s.y0??s.cy,s.y1??s.cy);return e.length?[(Math.min(...e)+Math.max(...e))/2,(Math.min(...i)+Math.max(...i))/2]:[0,0]}_vacDelta(t,e,i){const s=this._stageEl,o=s?.clientWidth||1,n=s?.clientHeight||1;return[e/o*t.w,i/n*t.h]}_vacFitPointer(t,e){const i=this._vacFit;if(!i)return;if(t.stopPropagation(),"pointerdown"===t.type){const e=t.target,s=e.getAttribute?.("data-corner");try{t.currentTarget.setPointerCapture?.(t.pointerId)}catch{}return void(this._vacFit={...i,drag:s?{kind:"scale",sx:t.clientX,sy:t.clientY,p0:{...i.p},fx:Number(s.split(",")[0]),fy:Number(s.split(",")[1])}:{kind:"move",sx:t.clientX,sy:t.clientY,p0:{...i.p},fx:0,fy:0}})}const s=i.drag;if(s){if("pointermove"===t.type){const[o,n]=this._vacDelta(e,t.clientX-s.sx,t.clientY-s.sy);if("move"===s.kind)this._vacFit={...i,p:{...s.p0,ox:s.p0.ox+o,oy:s.p0.oy+n}};else{const t=zi(this.hass?.states[i.source]?.attributes),e=this._vacGhostCentre(t?.rooms||[]),r=Ii(s.p0),[a,l]=Ci(r,e[0],e[1]),[c,h]=Ci(r,s.fx,s.fy),d=Math.hypot(a-c,l-h)||1,[p,u]=[2*a-c,2*l-h],_=Math.hypot(p+2*o-c,u+2*n-h)/2,m=Math.max(.05,_/d),g={...s.p0,s:s.p0.s*m};this._vacFit={...i,p:Li(g,s.p0,s.fx,s.fy)}}return}"pointerup"!==t.type&&"pointercancel"!==t.type||(this._vacFit={...i,drag:null})}}_renderVacFit(t){const e=this._vacFit;if(!e)return G;const i=zi(this.hass?.states[e.source]?.attributes);if(!i)return G;const s=Ii(e.p),o=[],n=[],r=[];for(const t of i.rooms){if(null==t.x0)continue;const e=[[t.x0,t.y0],[t.x1,t.y0],[t.x1,t.y1],[t.x0,t.y1]].map(([t,e])=>Ci(s,t,e));e.forEach(([t,e])=>{n.push(t),r.push(e)});const[i,a]=Ci(s,t.cx,t.cy);o.push(j`<polygon points="${e.map(t=>t[0].toFixed(1)+","+t[1].toFixed(1)).join(" ")}"></polygon>
|
||||
<text x="${i.toFixed(1)}" y="${a.toFixed(1)}">${t.name}</text>`)}let a=G;if(i.pos){const[e,o]=Ci(s,i.pos.x,i.pos.y);a=j`<circle class="vacfitdot" cx="${e.toFixed(1)}" cy="${o.toFixed(1)}" r="${(.012*t.w).toFixed(1)}"></circle>`}const l=[];if(n.length){const e=(()=>{const t=s[0]*s[4]-s[1]*s[3];return(e,i)=>[(s[4]*(e-s[2])-s[1]*(i-s[5]))/t,(-s[3]*(e-s[2])+s[0]*(i-s[5]))/t]})(),i=Math.min(...n),o=Math.max(...n),a=Math.min(...r),c=Math.max(...r),h=.014*t.w;for(const[t,s,n,r]of[[i,a,o,c],[o,a,i,c],[o,c,i,a],[i,c,o,a]]){const i=e(n,r);l.push(j`<circle class="vacfithandle" data-corner="${i[0]+","+i[1]}"
|
||||
<text x="${i.toFixed(1)}" y="${a.toFixed(1)}">${t.name}</text>`)}let a=G;if(i.pos){const[e,o]=Ci(s,i.pos.x,i.pos.y);a=j`<circle class="vacfitdot" cx="${e.toFixed(1)}" cy="${o.toFixed(1)}" r="${(.012*t.w).toFixed(1)}"></circle>`}const l=[];if(n.length){const e=(()=>{const t=s[0]*s[4]-s[1]*s[3];return(e,i)=>[(s[4]*(e-s[2])-s[1]*(i-s[5]))/t,(-s[3]*(e-s[2])+s[0]*(i-s[5]))/t]})(),i=Math.min(...n),o=Math.max(...n),a=Math.min(...r),c=Math.max(...r),h=.022*t.w;for(const[t,s,n,r]of[[i,a,o,c],[o,a,i,c],[o,c,i,a],[i,c,o,a]]){const i=e(n,r);l.push(j`<circle class="vacfithandle" data-corner="${i[0]+","+i[1]}"
|
||||
cx="${t.toFixed(1)}" cy="${s.toFixed(1)}" r="${h.toFixed(1)}"></circle>`)}}return B`<svg class="vacfit" viewBox="${t.x} ${t.y} ${t.w} ${t.h}"
|
||||
preserveAspectRatio="none"
|
||||
@pointerdown=${e=>this._vacFitPointer(e,t)}
|
||||
|
||||
+15
-1
@@ -118,6 +118,20 @@ const out = await page.evaluate(async () => {
|
||||
o.fitGhostRooms = sr().querySelectorAll('.vacfit polygon').length === 2;
|
||||
o.fitLabels = [...sr().querySelectorAll('.vacfit text')].map((t) => t.textContent.trim()).join('|') === 'Кухня|Зал';
|
||||
o.fitHandles = sr().querySelectorAll('.vacfithandle').length === 4;
|
||||
// REAL hit-tests: what would an actual click land on? Synthetic dispatch
|
||||
// bypasses pointer-events, and that let the untouchable overlay pass once.
|
||||
const hitAt = (el) => {
|
||||
const r = el.getBoundingClientRect();
|
||||
let n = document.elementFromPoint(r.left + r.width / 2, r.top + r.height / 2);
|
||||
while (n && n.shadowRoot) {
|
||||
const deeper = n.shadowRoot.elementFromPoint(r.left + r.width / 2, r.top + r.height / 2);
|
||||
if (!deeper || deeper === n) break;
|
||||
n = deeper;
|
||||
}
|
||||
return n;
|
||||
};
|
||||
o.fitHandleHittable = hitAt(sr().querySelector('.vacfithandle'))?.classList?.contains('vacfithandle') === true;
|
||||
o.fitGhostHittable = !!hitAt(sr().querySelector('.vacfit polygon'))?.closest?.('.vacfit');
|
||||
o.fitBar = !!sr().querySelector('.vaccalbar');
|
||||
o.fitMirrorDefault = c._vacFit.p.mir === true;
|
||||
// drag: the ghost centre must follow; rotate: the centre must NOT move
|
||||
@@ -169,7 +183,7 @@ checkAll(out, {
|
||||
puckGoneWhenDocked: true, trailLingers: true, hiddenNoPuck: true,
|
||||
unknownMapNoPuck: true,
|
||||
fitDevFound: true, fitOverlay: true, fitGhostRooms: true, fitLabels: true,
|
||||
fitHandles: true, fitBar: true, fitMirrorDefault: true, fitDragMoves: true,
|
||||
fitHandles: true, fitHandleHittable: true, fitGhostHittable: true, fitBar: true, fitMirrorDefault: true, fitDragMoves: true,
|
||||
fitRotateKeepsCentre: true, fitCornerScales: true, fitSavedMatrix: true,
|
||||
fitClosed: true, oldWizardGone: true, puckAfterFit: true,
|
||||
});
|
||||
|
||||
@@ -1318,6 +1318,11 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow
|
||||
overflow: visible;
|
||||
touch-action: none;
|
||||
cursor: grab;
|
||||
/* the devlayer is pointer-events: none and every child opts back in —
|
||||
without this line real clicks flew straight through the overlay
|
||||
(owner: «уголки не кликабельны»; synthetic smoke events bypass
|
||||
hit-testing, which is why they lied) */
|
||||
pointer-events: auto;
|
||||
}
|
||||
.vacfit:active { cursor: grabbing; }
|
||||
.vacfit polygon {
|
||||
@@ -2176,7 +2181,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow
|
||||
${a.length?B`<div class="rhint">${ti(this._t("vac.cal_maps"),{maps:a.join(", ")})}</div>`:G}
|
||||
`:G}
|
||||
</div>`}_vacMapId(t,e){if("default"!==e.mapId)return e.mapId;const i=this._vacEntity(t),s=i?this.hass?.states[i]?.attributes?.selected_map:null;return s?String(s):"default"}_vacSaveMatrix(t,e,i,s){const o=this._serverCfg,n=o?.markers?.find(e=>e.id===t);if(!n)return;const r={...n.vacuum||{}};r.source=e,r.calibration={...r.calibration||{},[i]:s.map(t=>Number(t.toFixed(6)))},n.vacuum=r,this._regSignature="",this._saveConfig(),this.requestUpdate()}_vacAutoCalibrate(t){const e=this._vacSource(t),i=e?zi(this.hass?.states[e]?.attributes):null;if(!e||!i||i.rooms.length<3)return void this._showToast(this._t("vac.autocal_no_rooms"));const s=this._spaceModel(t.space),o=(s?.rooms||[]).filter(t=>t.name&&t.poly?.length>=3).map(t=>{const e=Ae(t.poly);return{name:t.name,cx:e[0],cy:e[1]}}),n=Ai(i.rooms,o);n?(n.residual>50&&this._showToast(ti(this._t("vac.autocal_res_warn"),{rooms:String(n.matched.length)})),this._vacSaveMatrix(t.id,e,this._vacMapId(t,i),n.matrix),this._showToast(ti(this._t("vac.autocal_done"),{rooms:String(n.matched.length)}))):this._showToast(this._t("vac.autocal_no_match"))}_vacStartFit(t){const e=this._vacSource(t),i=e?zi(this.hass?.states[e]?.attributes):null;if(!e||!i)return void this._showToast(this._t("vac.cal_need_pos"));const s=this._vacMapId(t,i),o=t.marker?.vacuum?.calibration?.[s],n=this._spaceModel(t.space),r=n?.vb||[0,0,Ds,Ds],a=o&&6===o.length&&function(t){const e=t[0]*t[4]-t[1]*t[3];if(!Number.isFinite(e)||Math.abs(e)<1e-12)return null;const i=e<0,s=Math.sqrt(Math.abs(e));let o=180*Math.atan2(-t[1],t[4])/Math.PI;return o=(90*Math.round(o/90)%360+360)%360,{ox:t[2],oy:t[5],s:s,rot:o,mir:i}}(o)||function(t,e){const i=[],s=[];for(const e of t)null!=e.x0?(i.push(e.x0,e.x1),s.push(e.y0,e.y1)):(i.push(e.cx),s.push(e.cy));if(!i.length)return{ox:e[0]+e[2]/2,oy:e[1]+e[3]/2,s:e[2]/1e4,rot:0,mir:!0};const o=Math.min(...i),n=Math.max(...i),r=Math.min(...s),a=Math.max(...s),l=Math.max(n-o,a-r)||1,c={ox:0,oy:0,s:.6*Math.min(e[2],e[3])/l,rot:0,mir:!0},h=Ii(c),[d,p]=Ci(h,(o+n)/2,(r+a)/2);return c.ox=e[0]+e[2]/2-d,c.oy=e[1]+e[3]/2-p,c}(i.rooms,r);this._markerDialog=null,t.space!==this._space&&(this._space=t.space),this._vacFit={markerId:t.id,source:e,mapId:s,p:a,drag:null}}_vacFitSave(){const t=this._vacFit;t&&(this._vacSaveMatrix(t.markerId,t.source,t.mapId,Ii(t.p)),this._vacFit=null,this._showToast(this._t("vac.cal_done")))}_vacFitTurn(t){const e=this._vacFit;if(!e)return;const i=zi(this.hass?.states[e.source]?.attributes),s=this._vacGhostCentre(i?.rooms||[]),o={...e.p,...t};this._vacFit={...e,p:Li(o,e.p,s[0],s[1])}}_vacGhostCentre(t){const e=[],i=[];for(const s of t)e.push(s.x0??s.cx,s.x1??s.cx),i.push(s.y0??s.cy,s.y1??s.cy);return e.length?[(Math.min(...e)+Math.max(...e))/2,(Math.min(...i)+Math.max(...i))/2]:[0,0]}_vacDelta(t,e,i){const s=this._stageEl,o=s?.clientWidth||1,n=s?.clientHeight||1;return[e/o*t.w,i/n*t.h]}_vacFitPointer(t,e){const i=this._vacFit;if(!i)return;if(t.stopPropagation(),"pointerdown"===t.type){const e=t.target,s=e.getAttribute?.("data-corner");try{t.currentTarget.setPointerCapture?.(t.pointerId)}catch{}return void(this._vacFit={...i,drag:s?{kind:"scale",sx:t.clientX,sy:t.clientY,p0:{...i.p},fx:Number(s.split(",")[0]),fy:Number(s.split(",")[1])}:{kind:"move",sx:t.clientX,sy:t.clientY,p0:{...i.p},fx:0,fy:0}})}const s=i.drag;if(s){if("pointermove"===t.type){const[o,n]=this._vacDelta(e,t.clientX-s.sx,t.clientY-s.sy);if("move"===s.kind)this._vacFit={...i,p:{...s.p0,ox:s.p0.ox+o,oy:s.p0.oy+n}};else{const t=zi(this.hass?.states[i.source]?.attributes),e=this._vacGhostCentre(t?.rooms||[]),r=Ii(s.p0),[a,l]=Ci(r,e[0],e[1]),[c,h]=Ci(r,s.fx,s.fy),d=Math.hypot(a-c,l-h)||1,[p,u]=[2*a-c,2*l-h],_=Math.hypot(p+2*o-c,u+2*n-h)/2,m=Math.max(.05,_/d),g={...s.p0,s:s.p0.s*m};this._vacFit={...i,p:Li(g,s.p0,s.fx,s.fy)}}return}"pointerup"!==t.type&&"pointercancel"!==t.type||(this._vacFit={...i,drag:null})}}_renderVacFit(t){const e=this._vacFit;if(!e)return G;const i=zi(this.hass?.states[e.source]?.attributes);if(!i)return G;const s=Ii(e.p),o=[],n=[],r=[];for(const t of i.rooms){if(null==t.x0)continue;const e=[[t.x0,t.y0],[t.x1,t.y0],[t.x1,t.y1],[t.x0,t.y1]].map(([t,e])=>Ci(s,t,e));e.forEach(([t,e])=>{n.push(t),r.push(e)});const[i,a]=Ci(s,t.cx,t.cy);o.push(j`<polygon points="${e.map(t=>t[0].toFixed(1)+","+t[1].toFixed(1)).join(" ")}"></polygon>
|
||||
<text x="${i.toFixed(1)}" y="${a.toFixed(1)}">${t.name}</text>`)}let a=G;if(i.pos){const[e,o]=Ci(s,i.pos.x,i.pos.y);a=j`<circle class="vacfitdot" cx="${e.toFixed(1)}" cy="${o.toFixed(1)}" r="${(.012*t.w).toFixed(1)}"></circle>`}const l=[];if(n.length){const e=(()=>{const t=s[0]*s[4]-s[1]*s[3];return(e,i)=>[(s[4]*(e-s[2])-s[1]*(i-s[5]))/t,(-s[3]*(e-s[2])+s[0]*(i-s[5]))/t]})(),i=Math.min(...n),o=Math.max(...n),a=Math.min(...r),c=Math.max(...r),h=.014*t.w;for(const[t,s,n,r]of[[i,a,o,c],[o,a,i,c],[o,c,i,a],[i,c,o,a]]){const i=e(n,r);l.push(j`<circle class="vacfithandle" data-corner="${i[0]+","+i[1]}"
|
||||
<text x="${i.toFixed(1)}" y="${a.toFixed(1)}">${t.name}</text>`)}let a=G;if(i.pos){const[e,o]=Ci(s,i.pos.x,i.pos.y);a=j`<circle class="vacfitdot" cx="${e.toFixed(1)}" cy="${o.toFixed(1)}" r="${(.012*t.w).toFixed(1)}"></circle>`}const l=[];if(n.length){const e=(()=>{const t=s[0]*s[4]-s[1]*s[3];return(e,i)=>[(s[4]*(e-s[2])-s[1]*(i-s[5]))/t,(-s[3]*(e-s[2])+s[0]*(i-s[5]))/t]})(),i=Math.min(...n),o=Math.max(...n),a=Math.min(...r),c=Math.max(...r),h=.022*t.w;for(const[t,s,n,r]of[[i,a,o,c],[o,a,i,c],[o,c,i,a],[i,c,o,a]]){const i=e(n,r);l.push(j`<circle class="vacfithandle" data-corner="${i[0]+","+i[1]}"
|
||||
cx="${t.toFixed(1)}" cy="${s.toFixed(1)}" r="${h.toFixed(1)}"></circle>`)}}return B`<svg class="vacfit" viewBox="${t.x} ${t.y} ${t.w} ${t.h}"
|
||||
preserveAspectRatio="none"
|
||||
@pointerdown=${e=>this._vacFitPointer(e,t)}
|
||||
|
||||
Vendored
+6
-1
@@ -1318,6 +1318,11 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow
|
||||
overflow: visible;
|
||||
touch-action: none;
|
||||
cursor: grab;
|
||||
/* the devlayer is pointer-events: none and every child opts back in —
|
||||
without this line real clicks flew straight through the overlay
|
||||
(owner: «уголки не кликабельны»; synthetic smoke events bypass
|
||||
hit-testing, which is why they lied) */
|
||||
pointer-events: auto;
|
||||
}
|
||||
.vacfit:active { cursor: grabbing; }
|
||||
.vacfit polygon {
|
||||
@@ -2176,7 +2181,7 @@ const t=globalThis,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow
|
||||
${a.length?B`<div class="rhint">${ti(this._t("vac.cal_maps"),{maps:a.join(", ")})}</div>`:G}
|
||||
`:G}
|
||||
</div>`}_vacMapId(t,e){if("default"!==e.mapId)return e.mapId;const i=this._vacEntity(t),s=i?this.hass?.states[i]?.attributes?.selected_map:null;return s?String(s):"default"}_vacSaveMatrix(t,e,i,s){const o=this._serverCfg,n=o?.markers?.find(e=>e.id===t);if(!n)return;const r={...n.vacuum||{}};r.source=e,r.calibration={...r.calibration||{},[i]:s.map(t=>Number(t.toFixed(6)))},n.vacuum=r,this._regSignature="",this._saveConfig(),this.requestUpdate()}_vacAutoCalibrate(t){const e=this._vacSource(t),i=e?zi(this.hass?.states[e]?.attributes):null;if(!e||!i||i.rooms.length<3)return void this._showToast(this._t("vac.autocal_no_rooms"));const s=this._spaceModel(t.space),o=(s?.rooms||[]).filter(t=>t.name&&t.poly?.length>=3).map(t=>{const e=Ae(t.poly);return{name:t.name,cx:e[0],cy:e[1]}}),n=Ai(i.rooms,o);n?(n.residual>50&&this._showToast(ti(this._t("vac.autocal_res_warn"),{rooms:String(n.matched.length)})),this._vacSaveMatrix(t.id,e,this._vacMapId(t,i),n.matrix),this._showToast(ti(this._t("vac.autocal_done"),{rooms:String(n.matched.length)}))):this._showToast(this._t("vac.autocal_no_match"))}_vacStartFit(t){const e=this._vacSource(t),i=e?zi(this.hass?.states[e]?.attributes):null;if(!e||!i)return void this._showToast(this._t("vac.cal_need_pos"));const s=this._vacMapId(t,i),o=t.marker?.vacuum?.calibration?.[s],n=this._spaceModel(t.space),r=n?.vb||[0,0,Ds,Ds],a=o&&6===o.length&&function(t){const e=t[0]*t[4]-t[1]*t[3];if(!Number.isFinite(e)||Math.abs(e)<1e-12)return null;const i=e<0,s=Math.sqrt(Math.abs(e));let o=180*Math.atan2(-t[1],t[4])/Math.PI;return o=(90*Math.round(o/90)%360+360)%360,{ox:t[2],oy:t[5],s:s,rot:o,mir:i}}(o)||function(t,e){const i=[],s=[];for(const e of t)null!=e.x0?(i.push(e.x0,e.x1),s.push(e.y0,e.y1)):(i.push(e.cx),s.push(e.cy));if(!i.length)return{ox:e[0]+e[2]/2,oy:e[1]+e[3]/2,s:e[2]/1e4,rot:0,mir:!0};const o=Math.min(...i),n=Math.max(...i),r=Math.min(...s),a=Math.max(...s),l=Math.max(n-o,a-r)||1,c={ox:0,oy:0,s:.6*Math.min(e[2],e[3])/l,rot:0,mir:!0},h=Ii(c),[d,p]=Ci(h,(o+n)/2,(r+a)/2);return c.ox=e[0]+e[2]/2-d,c.oy=e[1]+e[3]/2-p,c}(i.rooms,r);this._markerDialog=null,t.space!==this._space&&(this._space=t.space),this._vacFit={markerId:t.id,source:e,mapId:s,p:a,drag:null}}_vacFitSave(){const t=this._vacFit;t&&(this._vacSaveMatrix(t.markerId,t.source,t.mapId,Ii(t.p)),this._vacFit=null,this._showToast(this._t("vac.cal_done")))}_vacFitTurn(t){const e=this._vacFit;if(!e)return;const i=zi(this.hass?.states[e.source]?.attributes),s=this._vacGhostCentre(i?.rooms||[]),o={...e.p,...t};this._vacFit={...e,p:Li(o,e.p,s[0],s[1])}}_vacGhostCentre(t){const e=[],i=[];for(const s of t)e.push(s.x0??s.cx,s.x1??s.cx),i.push(s.y0??s.cy,s.y1??s.cy);return e.length?[(Math.min(...e)+Math.max(...e))/2,(Math.min(...i)+Math.max(...i))/2]:[0,0]}_vacDelta(t,e,i){const s=this._stageEl,o=s?.clientWidth||1,n=s?.clientHeight||1;return[e/o*t.w,i/n*t.h]}_vacFitPointer(t,e){const i=this._vacFit;if(!i)return;if(t.stopPropagation(),"pointerdown"===t.type){const e=t.target,s=e.getAttribute?.("data-corner");try{t.currentTarget.setPointerCapture?.(t.pointerId)}catch{}return void(this._vacFit={...i,drag:s?{kind:"scale",sx:t.clientX,sy:t.clientY,p0:{...i.p},fx:Number(s.split(",")[0]),fy:Number(s.split(",")[1])}:{kind:"move",sx:t.clientX,sy:t.clientY,p0:{...i.p},fx:0,fy:0}})}const s=i.drag;if(s){if("pointermove"===t.type){const[o,n]=this._vacDelta(e,t.clientX-s.sx,t.clientY-s.sy);if("move"===s.kind)this._vacFit={...i,p:{...s.p0,ox:s.p0.ox+o,oy:s.p0.oy+n}};else{const t=zi(this.hass?.states[i.source]?.attributes),e=this._vacGhostCentre(t?.rooms||[]),r=Ii(s.p0),[a,l]=Ci(r,e[0],e[1]),[c,h]=Ci(r,s.fx,s.fy),d=Math.hypot(a-c,l-h)||1,[p,u]=[2*a-c,2*l-h],_=Math.hypot(p+2*o-c,u+2*n-h)/2,m=Math.max(.05,_/d),g={...s.p0,s:s.p0.s*m};this._vacFit={...i,p:Li(g,s.p0,s.fx,s.fy)}}return}"pointerup"!==t.type&&"pointercancel"!==t.type||(this._vacFit={...i,drag:null})}}_renderVacFit(t){const e=this._vacFit;if(!e)return G;const i=zi(this.hass?.states[e.source]?.attributes);if(!i)return G;const s=Ii(e.p),o=[],n=[],r=[];for(const t of i.rooms){if(null==t.x0)continue;const e=[[t.x0,t.y0],[t.x1,t.y0],[t.x1,t.y1],[t.x0,t.y1]].map(([t,e])=>Ci(s,t,e));e.forEach(([t,e])=>{n.push(t),r.push(e)});const[i,a]=Ci(s,t.cx,t.cy);o.push(j`<polygon points="${e.map(t=>t[0].toFixed(1)+","+t[1].toFixed(1)).join(" ")}"></polygon>
|
||||
<text x="${i.toFixed(1)}" y="${a.toFixed(1)}">${t.name}</text>`)}let a=G;if(i.pos){const[e,o]=Ci(s,i.pos.x,i.pos.y);a=j`<circle class="vacfitdot" cx="${e.toFixed(1)}" cy="${o.toFixed(1)}" r="${(.012*t.w).toFixed(1)}"></circle>`}const l=[];if(n.length){const e=(()=>{const t=s[0]*s[4]-s[1]*s[3];return(e,i)=>[(s[4]*(e-s[2])-s[1]*(i-s[5]))/t,(-s[3]*(e-s[2])+s[0]*(i-s[5]))/t]})(),i=Math.min(...n),o=Math.max(...n),a=Math.min(...r),c=Math.max(...r),h=.014*t.w;for(const[t,s,n,r]of[[i,a,o,c],[o,a,i,c],[o,c,i,a],[i,c,o,a]]){const i=e(n,r);l.push(j`<circle class="vacfithandle" data-corner="${i[0]+","+i[1]}"
|
||||
<text x="${i.toFixed(1)}" y="${a.toFixed(1)}">${t.name}</text>`)}let a=G;if(i.pos){const[e,o]=Ci(s,i.pos.x,i.pos.y);a=j`<circle class="vacfitdot" cx="${e.toFixed(1)}" cy="${o.toFixed(1)}" r="${(.012*t.w).toFixed(1)}"></circle>`}const l=[];if(n.length){const e=(()=>{const t=s[0]*s[4]-s[1]*s[3];return(e,i)=>[(s[4]*(e-s[2])-s[1]*(i-s[5]))/t,(-s[3]*(e-s[2])+s[0]*(i-s[5]))/t]})(),i=Math.min(...n),o=Math.max(...n),a=Math.min(...r),c=Math.max(...r),h=.022*t.w;for(const[t,s,n,r]of[[i,a,o,c],[o,a,i,c],[o,c,i,a],[i,c,o,a]]){const i=e(n,r);l.push(j`<circle class="vacfithandle" data-corner="${i[0]+","+i[1]}"
|
||||
cx="${t.toFixed(1)}" cy="${s.toFixed(1)}" r="${h.toFixed(1)}"></circle>`)}}return B`<svg class="vacfit" viewBox="${t.x} ${t.y} ${t.w} ${t.h}"
|
||||
preserveAspectRatio="none"
|
||||
@pointerdown=${e=>this._vacFitPointer(e,t)}
|
||||
|
||||
@@ -4637,7 +4637,7 @@ class HouseplanCard extends LitElement {
|
||||
})();
|
||||
const x0 = Math.min(...xs), x1 = Math.max(...xs);
|
||||
const y0 = Math.min(...ys), y1 = Math.max(...ys);
|
||||
const r = view.w * 0.014;
|
||||
const r = view.w * 0.022; // finger-sized: these are grabbed on tablets
|
||||
for (const [hx, hy, ox2, oy2] of [[x0, y0, x1, y1], [x1, y0, x0, y1], [x1, y1, x0, y0], [x0, y1, x1, y0]] as number[][]) {
|
||||
const fixed = inv(ox2, oy2);
|
||||
handles.push(svg`<circle class="vacfithandle" data-corner="${fixed[0] + ',' + fixed[1]}"
|
||||
|
||||
@@ -1315,6 +1315,11 @@ export const cardStyles = css`
|
||||
overflow: visible;
|
||||
touch-action: none;
|
||||
cursor: grab;
|
||||
/* the devlayer is pointer-events: none and every child opts back in —
|
||||
without this line real clicks flew straight through the overlay
|
||||
(owner: «уголки не кликабельны»; synthetic smoke events bypass
|
||||
hit-testing, which is why they lied) */
|
||||
pointer-events: auto;
|
||||
}
|
||||
.vacfit:active { cursor: grabbing; }
|
||||
.vacfit polygon {
|
||||
|
||||
Reference in New Issue
Block a user