mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
119 lines
6.0 KiB
JavaScript
119 lines
6.0 KiB
JavaScript
import { launch, checkAll, finish } from './serve.mjs';
|
|
const { page, browser } = await launch();
|
|
const res = await page.evaluate(async () => {
|
|
const out = {};
|
|
const c = window.__card;
|
|
const sr = () => c.shadowRoot || c.renderRoot;
|
|
c._setMode('plan'); c._tool = 'boundary'; await c.updateComplete;
|
|
// Shared wall r1|r2 at x=550, y from 140..460. Two-click open full shared stretch.
|
|
c._boundaryClick([550, 140]);
|
|
c._boundaryClick([550, 460]);
|
|
await c.updateComplete;
|
|
const r1 = c._curSpaceCfg.rooms.find((r) => r.id === 'r1');
|
|
const r2 = c._curSpaceCfg.rooms.find((r) => r.id === 'r2');
|
|
out.linked = (r1.open_to || []).includes('r2') && (r2.open_to || []).includes('r1');
|
|
out.hasSpans = Array.isArray(c._curSpaceCfg.open_spans) && c._curSpaceCfg.open_spans.length > 0;
|
|
out.dashes = sr().querySelectorAll('.openwall').length > 0;
|
|
const boundaryButtons = [...sr().querySelectorAll('.editbar button')]
|
|
.filter((b) => /Boundary|Граница/.test(b.textContent || ''));
|
|
out.oneBoundaryTool = boundaryButtons.length === 1;
|
|
out.boundaryToolUsesReviewedIcon = boundaryButtons[0]?.querySelector('ha-icon')
|
|
?.getAttribute('icon') === 'mdi:border-style';
|
|
// View: trimmed outlines + dash
|
|
c._setMode('view'); await c.updateComplete;
|
|
out.noedge = sr().querySelectorAll('.room.noedge').length >= 2;
|
|
out.trimmedOutline = sr().querySelectorAll('.room-outline').length >= 2;
|
|
const svgEl = sr().querySelector('svg');
|
|
const order = [...svgEl.children].map((el) => el.classList?.[0] || el.tagName);
|
|
const gi = order.indexOf('glowlayer');
|
|
const oi = order.indexOf('openwalls');
|
|
out.dashAboveGlow = gi === -1 || oi > gi;
|
|
c._setMode('plan'); c._tool = 'draw'; await c.updateComplete;
|
|
out.planDashes = sr().querySelectorAll('.openwall').length > 0;
|
|
out.planNoedge = sr().querySelectorAll('.room.noedge').length >= 2;
|
|
out.planBlueOutline = sr().querySelectorAll('.room-outline.outlined').length >= 2;
|
|
const midY = 300;
|
|
out.planSegCut = ![...sr().querySelectorAll('line.seg')].some((l) => {
|
|
const x1 = +l.getAttribute('x1'), x2 = +l.getAttribute('x2');
|
|
const y1 = +l.getAttribute('y1'), y2 = +l.getAttribute('y2');
|
|
return Math.abs(x1 - 550) < 0.5 && Math.abs(x2 - 550) < 0.5
|
|
&& Math.min(y1, y2) < midY && Math.max(y1, y2) > midY;
|
|
});
|
|
c._tool = 'boundary';
|
|
c._boundaryClick([550, 300]); await c.updateComplete;
|
|
out.restoredBySameTool = !(c._curSpaceCfg.open_spans || []).length;
|
|
out.toggledOff = !(c._curSpaceCfg.rooms.find((r) => r.id === 'r1').open_to || []).includes('r2');
|
|
out.dashesGone = sr().querySelectorAll('.openwall').length === 0;
|
|
c._boundaryClick([100, 100]);
|
|
out.missToast = !!c._toast;
|
|
// reopen for glow
|
|
c._boundaryClick([550, 140]);
|
|
c._boundaryClick([550, 460]);
|
|
await c.updateComplete;
|
|
c._setMode('view'); await c.updateComplete;
|
|
// 9 m of reach: the question here is whether light CROSSES the virtual
|
|
// boundary, so the lamp must have enough radius to arrive at the far room.
|
|
c._serverCfg = {
|
|
...c._serverCfg,
|
|
settings: { ...(c._serverCfg.settings || {}), glow_radius_cm: 900 },
|
|
spaces: c._serverCfg.spaces.map((s) => s.id !== c._space ? s : ({
|
|
...s, settings: { ...(s.settings || {}), fill_mode: 'glow' } })),
|
|
};
|
|
const litLight = c._devices.find((d) => d.space === c._space && d.entities.some((e) => e.startsWith('light.') && c.hass.states[e]?.state === 'on'));
|
|
const c1 = c._roomCenter(c._spaceModel().rooms.find((r) => r.id === 'r1'));
|
|
c._layout = { ...c._layout, [litLight.id]: { s: c._space, x: c1[0] / 1000, y: c1[1] / 1000 } };
|
|
c.requestUpdate(); await c.updateComplete;
|
|
// A virtual boundary is simply not a wall: light crosses it, so the lit
|
|
// region of a lamp in r1 must reach past r1's own outline into r2. Counting
|
|
// clip children no longer says anything — there is one region, not one path
|
|
// per room.
|
|
// Every vertex of a visibility region sits ON a wall, so "is this floor
|
|
// lit?" has to be asked as a point-in-region test, not by looking at
|
|
// coordinates.
|
|
const litRings = () => [...sr().querySelectorAll('defs clipPath[id^="hp-glowclip"] path.glow-lit')]
|
|
.flatMap((p) => p.getAttribute('d').split('M').filter(Boolean).map((sub) => {
|
|
const numbers = (sub.match(/-?\d+(?:\.\d+)?/g) || []).map(Number);
|
|
const ring = [];
|
|
for (let i = 0; i + 1 < numbers.length; i += 2) ring.push([numbers[i], numbers[i + 1]]);
|
|
return ring;
|
|
}));
|
|
const isLit = (point) => litRings().some((ring) => {
|
|
let inside = false;
|
|
for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
|
|
const [xi, yi] = ring[i]; const [xj, yj] = ring[j];
|
|
if ((yi > point[1]) !== (yj > point[1])
|
|
&& point[0] < ((xj - xi) * (point[1] - yi)) / ((yj - yi) || 1e-12) + xi) inside = !inside;
|
|
}
|
|
return inside;
|
|
});
|
|
out.zoneClip = isLit(c._roomCenter(c._spaceModel().rooms.find((r) => r.id === 'r2')));
|
|
const rr2 = c._curSpaceCfg.rooms.find((r) => r.id === 'r2');
|
|
const rr3 = c._curSpaceCfg.rooms.find((r) => r.id === 'r3');
|
|
// Just inside r3, in line with the gap that is about to be opened.
|
|
const probeR3 = [802, 480];
|
|
const beforeReachesR3 = isLit(probeR3);
|
|
if (rr3) {
|
|
rr2.open_to = [...(rr2.open_to || []), 'r3'];
|
|
rr3.open_to = [...(rr3.open_to || []), 'r2'];
|
|
// legacy open_to without spans still expands on read for cuts — force spans for r2-r3
|
|
c._setMode('plan'); c._tool = 'boundary';
|
|
// shared r2|r3 at y=460, x 550..960
|
|
c._boundaryClick([700, 460]);
|
|
c._boundaryClick([900, 460]);
|
|
await c.updateComplete;
|
|
c._setMode('view'); await c.updateComplete;
|
|
// Two hops: through the r1|r2 boundary, then through the r2|r3 one. Only
|
|
// the part of r3 that the lamp can actually see through both gaps lights
|
|
// up — but that part must, and none of it did before.
|
|
out.transitive = !beforeReachesR3 && isLit(probeR3);
|
|
} else out.transitive = 'no r3';
|
|
// outer wall refuses open
|
|
c._setMode('plan'); c._tool = 'boundary';
|
|
c._toast = null;
|
|
c._boundaryClick([40, 300]);
|
|
out.outerToast = !!(c._toast && String(c._toast).length);
|
|
return out;
|
|
});
|
|
checkAll(res, { outerToast: true });
|
|
await finish(browser, res);
|