mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
106 lines
3.7 KiB
JavaScript
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));
|