Files
houseplan-card/demo/smoke_glow_fail_dark.mjs
2026-08-19 04:09:13 +03:00

106 lines
3.7 KiB
JavaScript

/** Issue #187: Glow source fallback uses light-policy bodies when boolean masonry fails. */
import { launch, checkAll, finish } from './serve.mjs';
const { page, browser } = await launch({ width: 900, height: 760 }, 1);
const out = await page.evaluate(async () => {
const result = {};
const card = window.__card;
const root = () => card.shadowRoot || card.renderRoot;
const update = async () => {
card.requestUpdate();
await card.updateComplete;
await new Promise((resolve) => setTimeout(resolve, 60));
await card.updateComplete;
};
const pointIn = (point, body) => {
let inside = false;
for (let i = 0, j = body.length - 1; i < body.length; j = i++) {
const a = body[i], b = body[j];
if (((a[1] > point[1]) !== (b[1] > point[1]))
&& point[0] < ((b[0] - a[0]) * (point[1] - a[1]))
/ ((b[1] - a[1]) || 1e-12) + a[0]) inside = !inside;
}
return inside;
};
const spaceId = card._space;
const lightDevice = card._devices.find((device) => device.space === spaceId
&& device.entities.some((entityId) => entityId.startsWith('light.')));
const sourceEid = lightDevice?.entities.find((entityId) => entityId.startsWith('light.'));
if (!lightDevice || !sourceEid) return {
fixtureHasLightSource: false,
windowFallbackBodyIsOpaque: false,
windowSourceSuppressedOnBooleanFailure: false,
interiorPassageRemainsTransparent: false,
};
result.fixtureHasLightSource = true;
const centre = [500, 500];
const opening = (type) => ({
id: 'hosted-opening', type, x: 0.5, y: 0.5, angle: 0, length: 0.12,
host: { kind: 'partition', id: 'host', t: 0.5 },
});
const currentState = card.hass.states[sourceEid];
card.hass = {
...card.hass,
states: {
...card.hass.states,
[sourceEid]: { ...currentState, state: 'on' },
},
};
card._layout = {
...card._layout,
[lightDevice.id]: { s: spaceId, x: 0.5, y: 0.5 },
};
const replaceSpace = (type) => {
card._serverCfg = {
...card._serverCfg,
spaces: card._serverCfg.spaces.map((space) => space.id !== spaceId ? space : ({
...space,
rooms: [{
id: 'glow-room', name: 'Glow room', area: null,
poly: [[0.1, 0.1], [0.9, 0.1], [0.9, 0.9], [0.1, 0.9]],
}],
settings: { ...(space.settings || {}), fill_mode: 'glow' },
partitions: [{ id: 'host', a: [0.25, 0.5], b: [0.75, 0.5], cm: 15 }],
openings: [opening(type)],
room_drafts: [], wall_columns: [],
})),
};
card._cfgEpoch++;
card._modelCache = null;
card._physicalBodiesCache = null;
card._lightPhysicalBodiesCache = null;
card._lightBarrierCache = null;
card._renderDeviceSnapshot = null;
card._glowClipCache.clear();
};
// Simulate wallBodiesGeometry() returning null after a clipping exception.
// The remainder of the real _lightBarriers path still computes the exact
// light-policy body set, then only its boolean geometry is made unavailable.
const realLightBarriers = card._lightBarriers.bind(card);
card._lightBarriers = (...args) => {
const barriers = realLightBarriers(...args);
if (result.windowFallbackBodyIsOpaque === undefined) {
result.windowFallbackBodyIsOpaque = barriers.opaqueBodies
.some((body) => pointIn(centre, body));
}
return { ...barriers, masonryGeometry: [] };
};
replaceSpace('window');
await update();
const selector = `[data-glow-source="${CSS.escape(sourceEid)}"] .glow-pool`;
result.windowSourceSuppressedOnBooleanFailure = !root().querySelector(selector);
replaceSpace('passage');
await update();
result.interiorPassageRemainsTransparent = !!root().querySelector(selector);
return result;
});
await finish(browser, checkAll(out));